summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2020-04-28 05:31:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2020-04-28 05:31:41 +0000
commit4825c90b8299e0ee4ee27c2529820bad8b64c3b1 (patch)
tree0362830820537da5f1764d96e88ab8d2e7227827
parentReleasing debian version 2.2.0-2. (diff)
downloadpre-commit-4825c90b8299e0ee4ee27c2529820bad8b64c3b1.tar.xz
pre-commit-4825c90b8299e0ee4ee27c2529820bad8b64c3b1.zip
Merging upstream version 2.3.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.pre-commit-config.yaml14
-rw-r--r--CHANGELOG.md27
-rw-r--r--pre_commit/color.py10
-rw-r--r--pre_commit/commands/autoupdate.py4
-rw-r--r--pre_commit/commands/hook_impl.py31
-rw-r--r--pre_commit/commands/run.py12
-rw-r--r--pre_commit/languages/docker.py8
-rw-r--r--pre_commit/languages/node.py16
-rw-r--r--pre_commit/languages/python.py6
-rw-r--r--setup.cfg2
-rw-r--r--tests/color_test.py8
-rw-r--r--tests/commands/autoupdate_test.py39
-rw-r--r--tests/commands/hook_impl_test.py45
-rw-r--r--tests/commands/run_test.py12
-rw-r--r--tests/languages/docker_test.py2
-rw-r--r--tests/languages/node_test.py47
-rw-r--r--tests/repository_test.py15
17 files changed, 261 insertions, 37 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c2df486..b51417d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,34 +17,34 @@ repos:
- id: flake8
additional_dependencies: [flake8-typing-imports==1.6.0]
- repo: https://github.com/pre-commit/mirrors-autopep8
- rev: v1.5
+ rev: v1.5.1
hooks:
- id: autopep8
- repo: https://github.com/pre-commit/pre-commit
- rev: v2.1.1
+ rev: v2.2.0
hooks:
- id: validate_manifest
- repo: https://github.com/asottile/pyupgrade
- rev: v2.0.1
+ rev: v2.1.0
hooks:
- id: pyupgrade
args: [--py36-plus]
- repo: https://github.com/asottile/reorder_python_imports
- rev: v1.9.0
+ rev: v2.1.0
hooks:
- id: reorder-python-imports
args: [--py3-plus]
- repo: https://github.com/asottile/add-trailing-comma
- rev: v1.5.0
+ rev: v2.0.1
hooks:
- id: add-trailing-comma
args: [--py36-plus]
- repo: https://github.com/asottile/setup-cfg-fmt
- rev: v1.6.0
+ rev: v1.8.2
hooks:
- id: setup-cfg-fmt
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.761
+ rev: v0.770
hooks:
- id: mypy
exclude: ^testing/resources/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a6892c..5b83319 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,34 @@
+2.3.0 - 2020-04-22
+==================
+
+### Features
+- Calculate character width using `east_asian_width`
+ - #1378 PR by @sophgn.
+- Use `language_version: system` by default for `node` hooks if `node` / `npm`
+ are globally installed.
+ - #1388 PR by @asottile.
+
+### Fixes
+- No longer use a hard-coded user id for docker hooks on windows
+ - #1371 PR by @killuazhu.
+- Fix colors on windows during `git commit`
+ - #1381 issue by @Cielquan.
+ - #1382 PR by @asottile.
+- Produce readable error message for incorrect argument count to `hook-impl`
+ - #1394 issue by @pip9ball.
+ - #1395 PR by @asottile.
+- Fix installations which involve an upgrade of `pip` on windows
+ - #1398 issue by @xiaohuazi123.
+ - #1399 PR by @asottile.
+- Preserve line endings in `pre-commit autoupdate`
+ - #1402 PR by @utek.
+
2.2.0 - 2020-03-12
==================
### Features
- Add support for the `post-checkout` hook
- - #1210 issue by @domenkozar.
+ - #1120 issue by @domenkozar.
- #1339 PR by @andrewhare.
- Add more readable `--from-ref` / `--to-ref` aliases for `--source` /
`--origin`
diff --git a/pre_commit/color.py b/pre_commit/color.py
index 5fa7042..eb906b7 100644
--- a/pre_commit/color.py
+++ b/pre_commit/color.py
@@ -11,7 +11,7 @@ if sys.platform == 'win32': # pragma: no cover (windows)
from ctypes.wintypes import DWORD
from ctypes.wintypes import HANDLE
- STD_OUTPUT_HANDLE = -11
+ STD_ERROR_HANDLE = -12
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
def bool_errcheck(result, func, args):
@@ -40,9 +40,9 @@ if sys.platform == 'win32': # pragma: no cover (windows)
#
# More info on the escape sequences supported:
# https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
- stdout = GetStdHandle(STD_OUTPUT_HANDLE)
- flags = GetConsoleMode(stdout)
- SetConsoleMode(stdout, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+ stderr = GetStdHandle(STD_ERROR_HANDLE)
+ flags = GetConsoleMode(stderr)
+ SetConsoleMode(stderr, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
try:
_enable()
@@ -90,7 +90,7 @@ def use_color(setting: str) -> bool:
return (
setting == 'always' or (
setting == 'auto' and
- sys.stdout.isatty() and
+ sys.stderr.isatty() and
terminal_supports_color and
os.getenv('TERM') != 'dumb'
)
diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py
index 5a9a988..8c9fdd7 100644
--- a/pre_commit/commands/autoupdate.py
+++ b/pre_commit/commands/autoupdate.py
@@ -93,7 +93,7 @@ def _original_lines(
retry: bool = False,
) -> Tuple[List[str], List[int]]:
"""detect `rev:` lines or reformat the file"""
- with open(path) as f:
+ with open(path, newline='') as f:
original = f.read()
lines = original.splitlines(True)
@@ -126,7 +126,7 @@ def _write_new_config(path: str, rev_infos: List[Optional[RevInfo]]) -> None:
comment = match[4]
lines[idx] = f'{match[1]}rev:{match[2]}{new_rev}{comment}{match[5]}'
- with open(path, 'w') as f:
+ with open(path, 'w', newline='') as f:
f.write(''.join(lines))
diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py
index 5ff4555..4843fc7 100644
--- a/pre_commit/commands/hook_impl.py
+++ b/pre_commit/commands/hook_impl.py
@@ -147,15 +147,44 @@ def _pre_push_ns(
return None
+_EXPECTED_ARG_LENGTH_BY_HOOK = {
+ 'commit-msg': 1,
+ 'post-checkout': 3,
+ 'pre-commit': 0,
+ 'pre-merge-commit': 0,
+ 'pre-push': 2,
+}
+
+
+def _check_args_length(hook_type: str, args: Sequence[str]) -> None:
+ if hook_type == 'prepare-commit-msg':
+ if len(args) < 1 or len(args) > 3:
+ raise SystemExit(
+ f'hook-impl for {hook_type} expected 1, 2, or 3 arguments '
+ f'but got {len(args)}: {args}',
+ )
+ elif hook_type in _EXPECTED_ARG_LENGTH_BY_HOOK:
+ expected = _EXPECTED_ARG_LENGTH_BY_HOOK[hook_type]
+ if len(args) != expected:
+ arguments_s = 'argument' if expected == 1 else 'arguments'
+ raise SystemExit(
+ f'hook-impl for {hook_type} expected {expected} {arguments_s} '
+ f'but got {len(args)}: {args}',
+ )
+ else:
+ raise AssertionError(f'unexpected hook type: {hook_type}')
+
+
def _run_ns(
hook_type: str,
color: bool,
args: Sequence[str],
stdin: bytes,
) -> Optional[argparse.Namespace]:
+ _check_args_length(hook_type, args)
if hook_type == 'pre-push':
return _pre_push_ns(color, args, stdin)
- elif hook_type in {'prepare-commit-msg', 'commit-msg'}:
+ elif hook_type in {'commit-msg', 'prepare-commit-msg'}:
return _ns(hook_type, color, commit_msg_filename=args[0])
elif hook_type in {'pre-merge-commit', 'pre-commit'}:
return _ns(hook_type, color)
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index 2f74578..8c8401c 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -6,6 +6,7 @@ import os
import re
import subprocess
import time
+import unicodedata
from typing import Any
from typing import Collection
from typing import Dict
@@ -33,8 +34,13 @@ from pre_commit.util import EnvironT
logger = logging.getLogger('pre_commit')
+def _len_cjk(msg: str) -> int:
+ widths = {'A': 1, 'F': 2, 'H': 1, 'N': 1, 'Na': 1, 'W': 2}
+ return sum(widths[unicodedata.east_asian_width(c)] for c in msg)
+
+
def _start_msg(*, start: str, cols: int, end_len: int) -> str:
- dots = '.' * (cols - len(start) - end_len - 1)
+ dots = '.' * (cols - _len_cjk(start) - end_len - 1)
return f'{start}{dots}'
@@ -47,7 +53,7 @@ def _full_msg(
use_color: bool,
postfix: str = '',
) -> str:
- dots = '.' * (cols - len(start) - len(postfix) - len(end_msg) - 1)
+ dots = '.' * (cols - _len_cjk(start) - len(postfix) - len(end_msg) - 1)
end = color.format_color(end_msg, end_color, use_color)
return f'{start}{dots}{postfix}{end}\n'
@@ -206,7 +212,7 @@ def _compute_cols(hooks: Sequence[Hook]) -> int:
Hook name...(no files to check) Skipped
"""
if hooks:
- name_len = max(len(hook.name) for hook in hooks)
+ name_len = max(_len_cjk(hook.name) for hook in hooks)
else:
name_len = 0
diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py
index f449584..4091492 100644
--- a/pre_commit/languages/docker.py
+++ b/pre_commit/languages/docker.py
@@ -76,18 +76,18 @@ def install_environment(
os.mkdir(directory)
-def get_docker_user() -> str: # pragma: win32 no cover
+def get_docker_user() -> Tuple[str, ...]: # pragma: win32 no cover
try:
- return f'{os.getuid()}:{os.getgid()}'
+ return ('-u', f'{os.getuid()}:{os.getgid()}')
except AttributeError:
- return '1000:1000'
+ return ()
def docker_cmd() -> Tuple[str, ...]: # pragma: win32 no cover
return (
'docker', 'run',
'--rm',
- '-u', get_docker_user(),
+ *get_docker_user(),
# https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from
# The `Z` option tells Docker to label the content with a private
# unshared label. Only the current container can use a private volume.
diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py
index 79ff807..9b636d3 100644
--- a/pre_commit/languages/node.py
+++ b/pre_commit/languages/node.py
@@ -1,4 +1,5 @@
import contextlib
+import functools
import os
import sys
from typing import Generator
@@ -6,6 +7,7 @@ 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 Var
@@ -18,10 +20,22 @@ from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
ENVIRONMENT_DIR = 'node_env'
-get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
+@functools.lru_cache(maxsize=1)
+def get_default_version() -> str:
+ # nodeenv does not yet support `-n system` on windows
+ if sys.platform == 'win32':
+ 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')):
+ return 'system'
+ else:
+ return C.DEFAULT
+
+
def _envdir(prefix: Prefix, version: str) -> str:
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
return prefix.path(directory)
diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py
index 5073a8b..85d8281 100644
--- a/pre_commit/languages/python.py
+++ b/pre_commit/languages/python.py
@@ -182,8 +182,8 @@ def py_interface(
version: str,
additional_dependencies: Sequence[str],
) -> None:
- additional_dependencies = tuple(additional_dependencies)
directory = helpers.environment_dir(_dir, version)
+ install = ('python', '-mpip', 'install', '.', *additional_dependencies)
env_dir = prefix.path(directory)
with clean_path_on_failure(env_dir):
@@ -193,9 +193,7 @@ def py_interface(
python = os.path.realpath(sys.executable)
_make_venv(env_dir, python)
with in_env(prefix, version):
- helpers.run_setup_cmd(
- prefix, ('pip', 'install', '.') + additional_dependencies,
- )
+ helpers.run_setup_cmd(prefix, install)
return in_env, healthy, run_hook, install_environment
diff --git a/setup.cfg b/setup.cfg
index a02fab1..2e69d50 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = pre_commit
-version = 2.2.0
+version = 2.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
diff --git a/tests/color_test.py b/tests/color_test.py
index 98b39c1..5cd226a 100644
--- a/tests/color_test.py
+++ b/tests/color_test.py
@@ -29,26 +29,26 @@ def test_use_color_always():
def test_use_color_no_tty():
- with mock.patch.object(sys.stdout, 'isatty', return_value=False):
+ with mock.patch.object(sys.stderr, 'isatty', return_value=False):
assert use_color('auto') is False
def test_use_color_tty_with_color_support():
- with mock.patch.object(sys.stdout, 'isatty', return_value=True):
+ with mock.patch.object(sys.stderr, 'isatty', return_value=True):
with mock.patch('pre_commit.color.terminal_supports_color', True):
with envcontext.envcontext((('TERM', envcontext.UNSET),)):
assert use_color('auto') is True
def test_use_color_tty_without_color_support():
- with mock.patch.object(sys.stdout, 'isatty', return_value=True):
+ with mock.patch.object(sys.stderr, 'isatty', return_value=True):
with mock.patch('pre_commit.color.terminal_supports_color', False):
with envcontext.envcontext((('TERM', envcontext.UNSET),)):
assert use_color('auto') is False
def test_use_color_dumb_term():
- with mock.patch.object(sys.stdout, 'isatty', return_value=True):
+ with mock.patch.object(sys.stderr, 'isatty', return_value=True):
with mock.patch('pre_commit.color.terminal_supports_color', True):
with envcontext.envcontext((('TERM', 'dumb'),)):
assert use_color('auto') is False
diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py
index 2c7b2f1..25161d1 100644
--- a/tests/commands/autoupdate_test.py
+++ b/tests/commands/autoupdate_test.py
@@ -263,6 +263,45 @@ def test_does_not_reformat(tmpdir, out_of_date, store):
assert cfg.read() == expected
+def test_does_not_change_mixed_endlines_read(up_to_date, tmpdir, store):
+ fmt = (
+ 'repos:\n'
+ '- repo: {}\n'
+ ' rev: {} # definitely the version I want!\r\n'
+ ' hooks:\r\n'
+ ' - id: foo\n'
+ ' # These args are because reasons!\r\n'
+ ' args: [foo, bar, baz]\r\n'
+ )
+ cfg = tmpdir.join(C.CONFIG_FILE)
+
+ expected = fmt.format(up_to_date, git.head_rev(up_to_date)).encode()
+ cfg.write_binary(expected)
+
+ assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0
+ assert cfg.read_binary() == expected
+
+
+def test_does_not_change_mixed_endlines_write(tmpdir, out_of_date, store):
+ fmt = (
+ 'repos:\n'
+ '- repo: {}\n'
+ ' rev: {} # definitely the version I want!\r\n'
+ ' hooks:\r\n'
+ ' - id: foo\n'
+ ' # These args are because reasons!\r\n'
+ ' args: [foo, bar, baz]\r\n'
+ )
+ cfg = tmpdir.join(C.CONFIG_FILE)
+ cfg.write_binary(
+ fmt.format(out_of_date.path, out_of_date.original_rev).encode(),
+ )
+
+ assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0
+ expected = fmt.format(out_of_date.path, out_of_date.head_rev).encode()
+ assert cfg.read_binary() == expected
+
+
def test_loses_formatting_when_not_detectable(out_of_date, store, tmpdir):
"""A best-effort attempt is made at updating rev without rewriting
formatting. When the original formatting cannot be detected, this
diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py
index 032fa8f..ddf65b7 100644
--- a/tests/commands/hook_impl_test.py
+++ b/tests/commands/hook_impl_test.py
@@ -89,6 +89,51 @@ def test_run_legacy_recursive(tmpdir):
call()
+@pytest.mark.parametrize(
+ ('hook_type', 'args'),
+ (
+ ('pre-commit', []),
+ ('pre-merge-commit', []),
+ ('pre-push', ['branch_name', 'remote_name']),
+ ('commit-msg', ['.git/COMMIT_EDITMSG']),
+ ('post-checkout', ['old_head', 'new_head', '1']),
+ # multiple choices for commit-editmsg
+ ('prepare-commit-msg', ['.git/COMMIT_EDITMSG']),
+ ('prepare-commit-msg', ['.git/COMMIT_EDITMSG', 'message']),
+ ('prepare-commit-msg', ['.git/COMMIT_EDITMSG', 'commit', 'deadbeef']),
+ ),
+)
+def test_check_args_length_ok(hook_type, args):
+ hook_impl._check_args_length(hook_type, args)
+
+
+def test_check_args_length_error_too_many_plural():
+ with pytest.raises(SystemExit) as excinfo:
+ hook_impl._check_args_length('pre-commit', ['run', '--all-files'])
+ msg, = excinfo.value.args
+ assert msg == (
+ 'hook-impl for pre-commit expected 0 arguments but got 2: '
+ "['run', '--all-files']"
+ )
+
+
+def test_check_args_length_error_too_many_singluar():
+ with pytest.raises(SystemExit) as excinfo:
+ hook_impl._check_args_length('commit-msg', [])
+ msg, = excinfo.value.args
+ assert msg == 'hook-impl for commit-msg expected 1 argument but got 0: []'
+
+
+def test_check_args_length_prepare_commit_msg_error():
+ with pytest.raises(SystemExit) as excinfo:
+ hook_impl._check_args_length('prepare-commit-msg', [])
+ msg, = excinfo.value.args
+ assert msg == (
+ 'hook-impl for prepare-commit-msg expected 1, 2, or 3 arguments '
+ 'but got 0: []'
+ )
+
+
def test_run_ns_pre_commit():
ns = hook_impl._run_ns('pre-commit', True, (), b'')
assert ns is not None
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index f8e8823..c51bcff 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -52,6 +52,18 @@ def test_full_msg():
assert ret == 'start......end\n'
+def test_full_msg_with_cjk():
+ ret = _full_msg(
+ start='啊あ아',
+ end_msg='end',
+ end_color='',
+ use_color=False,
+ cols=15,
+ )
+ # 5 dots: 15 - 6 - 3 - 1
+ assert ret == '啊あ아.....end\n'
+
+
def test_full_msg_with_color():
ret = _full_msg(
start='start',
diff --git a/tests/languages/docker_test.py b/tests/languages/docker_test.py
index 171a3f7..b65b223 100644
--- a/tests/languages/docker_test.py
+++ b/tests/languages/docker_test.py
@@ -20,4 +20,4 @@ def test_docker_fallback_user():
getuid=invalid_attribute,
getgid=invalid_attribute,
):
- assert docker.get_docker_user() == '1000:1000'
+ assert docker.get_docker_user() == ()
diff --git a/tests/languages/node_test.py b/tests/languages/node_test.py
new file mode 100644
index 0000000..fd30046
--- /dev/null
+++ b/tests/languages/node_test.py
@@ -0,0 +1,47 @@
+import sys
+from unittest import mock
+
+import pytest
+
+import pre_commit.constants as C
+from pre_commit import parse_shebang
+from pre_commit.languages.node import get_default_version
+
+
+ACTUAL_GET_DEFAULT_VERSION = get_default_version.__wrapped__
+
+
+@pytest.fixture
+def is_linux():
+ with mock.patch.object(sys, 'platform', 'linux'):
+ yield
+
+
+@pytest.fixture
+def is_win32():
+ with mock.patch.object(sys, 'platform', 'win32'):
+ yield
+
+
+@pytest.fixture
+def find_exe_mck():
+ with mock.patch.object(parse_shebang, 'find_executable') as mck:
+ yield mck
+
+
+@pytest.mark.usefixtures('is_linux')
+def test_sets_system_when_node_and_npm_are_available(find_exe_mck):
+ find_exe_mck.return_value = '/path/to/exe'
+ assert ACTUAL_GET_DEFAULT_VERSION() == 'system'
+
+
+@pytest.mark.usefixtures('is_linux')
+def test_uses_default_when_node_and_npm_are_not_available(find_exe_mck):
+ find_exe_mck.return_value = None
+ assert ACTUAL_GET_DEFAULT_VERSION() == C.DEFAULT
+
+
+@pytest.mark.usefixtures('is_win32')
+def test_sets_default_on_windows(find_exe_mck):
+ find_exe_mck.return_value = '/path/to/exe'
+ assert ACTUAL_GET_DEFAULT_VERSION() == C.DEFAULT
diff --git a/tests/repository_test.py b/tests/repository_test.py
index df7e7d1..3c7a637 100644
--- a/tests/repository_test.py
+++ b/tests/repository_test.py
@@ -131,9 +131,9 @@ def test_python_hook(tempdir_factory, store):
def test_python_hook_default_version(tempdir_factory, store):
# make sure that this continues to work for platforms where default
# language detection does not work
- with mock.patch.object(
- python, 'get_default_version', return_value=C.DEFAULT,
- ):
+ returns_default = mock.Mock(return_value=C.DEFAULT)
+ lang = languages['python']._replace(get_default_version=returns_default)
+ with mock.patch.dict(languages, python=lang):
test_python_hook(tempdir_factory, store)
@@ -243,6 +243,15 @@ def test_run_a_node_hook(tempdir_factory, store):
)
+def test_run_a_node_hook_default_version(tempdir_factory, store):
+ # make sure that this continues to work for platforms where node is not
+ # installed at the system
+ returns_default = mock.Mock(return_value=C.DEFAULT)
+ lang = languages['node']._replace(get_default_version=returns_default)
+ with mock.patch.dict(languages, node=lang):
+ test_run_a_node_hook(tempdir_factory, store)
+
+
def test_run_versioned_node_hook(tempdir_factory, store):
_test_hook_repo(
tempdir_factory, store, 'node_versioned_hooks_repo',