diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 32376b27..00000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -language: python -dist: xenial -services: - - docker -matrix: - include: - - env: TOXENV=py27 - - env: TOXENV=py27 LATEST_GIT=1 - - env: TOXENV=py36 - python: 3.6 - - env: TOXENV=pypy - python: pypy2.7-5.10.0 - - env: TOXENV=py37 - python: 3.7 -install: pip install coveralls tox -script: tox -before_install: - - git --version - - | - if [ "$LATEST_GIT" = "1" ]; then - testing/latest-git.sh - export PATH="/tmp/git/bin:$PATH" - fi - - git --version - - 'testing/get-swift.sh && export PATH="/tmp/swift/usr/bin:$PATH"' - - 'curl -sSf https://sh.rustup.rs | bash -s -- -y' - - export PATH="$HOME/.cargo/bin:$PATH" -after_success: coveralls -cache: - directories: - - $HOME/.cache/pip - - $HOME/.cache/pre-commit - - $HOME/.rustup - - $HOME/.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 129447a4..09f0fdaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,51 @@ +1.15.2 +====== + +### Fixes +- Fix cloning non-branch tag while in the fallback slow-clone strategy. + - #997 issue by @jpinner. + - #998 PR by @asottile. + +1.15.1 +====== + +### Fixes +- Fix command length calculation on posix when `SC_ARG_MAX` is not defined. + - #691 issue by @ushuz. + - #987 PR by @asottile. + +1.15.0 +====== + +### Features +- No longer require being in a `git` repo to run `pre-commit` `clean` / `gc` / + `sample-config`. + - #959 PR by @asottile. +- Improve command line length limit detection. + - #691 issue by @antonbabenko. + - #966 PR by @asottile. +- Use shallow cloning when possible. + - #958 PR by @DanielChabrowski. +- Add `minimum_pre_commit_version` top level key to require a new-enough + version of `pre-commit`. + - #977 PR by @asottile. +- Add helpful CI-friendly message when running + `pre-commit run --all-files --show-diff-on-failure`. + - #982 PR by @bnorquist. + +### Fixes +- Fix `try-repo` for staged untracked changes. + - #973 PR by @DanielChabrowski. +- Fix rpm build by explicitly using `#!/usr/bin/env python3` in hook template. + - #985 issue by @tim77. + - #986 PR by @tim77. +- Guard against infinite recursion when executing legacy hook script. + - #981 PR by @tristan0x. + +### Misc +- Add test for `git.no_git_env()` + - #972 PR by @javabrett. + 1.14.4 ====== diff --git a/README.md b/README.md index 12b222d3..01d0d757 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ -[![Build Status](https://travis-ci.org/pre-commit/pre-commit.svg?branch=master)](https://travis-ci.org/pre-commit/pre-commit) -[![Coverage Status](https://coveralls.io/repos/github/pre-commit/pre-commit/badge.svg?branch=master)](https://coveralls.io/github/pre-commit/pre-commit?branch=master) -[![Build status](https://ci.appveyor.com/api/projects/status/mmcwdlfgba4esaii/branch/master?svg=true)](https://ci.appveyor.com/project/asottile/pre-commit/branch/master) +[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/pre-commit.pre-commit?branchName=master)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/21/master.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=master) ## pre-commit diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 23d3931c..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,29 +0,0 @@ -environment: - global: - COVERAGE_IGNORE_WINDOWS: '# pragma: windows no cover' - TOX_TESTENV_PASSENV: COVERAGE_IGNORE_WINDOWS - matrix: - - TOXENV: py27 - - TOXENV: py37 - -install: - - "SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%" - - pip install tox virtualenv --upgrade - - "mkdir -p C:\\Temp" - - "SET TMPDIR=C:\\Temp" - - "curl -sSf https://sh.rustup.rs | bash -s -- -y" - - "SET PATH=%USERPROFILE%\\.cargo\\bin;%PATH%" - -# Not a C# project -build: false - -before_test: - # Shut up CRLF messages - - git config --global core.autocrlf false - - git config --global core.safecrlf false - -test_script: tox - -cache: - - '%LOCALAPPDATA%\pip\cache' - - '%USERPROFILE%\.cache\pre-commit' diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..ce09d9c4 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,50 @@ +trigger: + branches: + include: [master, test-me-*] + tags: + include: ['*'] + +resources: + repositories: + - repository: asottile + type: github + endpoint: github + name: asottile/azure-pipeline-templates + ref: refs/tags/v0.0.13 + +jobs: +- template: job--pre-commit.yml@asottile +- template: job--python-tox.yml@asottile + parameters: + toxenvs: [py27, py37] + os: windows + additional_variables: + COVERAGE_IGNORE_WINDOWS: '# pragma: windows no cover' + TOX_TESTENV_PASSENV: COVERAGE_IGNORE_WINDOWS + TEMP: C:\Temp # remove when dropping python2 + pre_test: + - template: step--rust-install.yml +- template: job--python-tox.yml@asottile + parameters: + toxenvs: [py37] + os: linux + name_postfix: _latest_git + pre_test: + - task: UseRubyVersion@0 + - template: step--git-install.yml + - template: step--rust-install.yml + - bash: | + testing/get-swift.sh + echo '##vso[task.prependpath]/tmp/swift/usr/bin' + displayName: install swift +- template: job--python-tox.yml@asottile + parameters: + toxenvs: [pypy, pypy3, py27, py36, py37] + os: linux + pre_test: + - task: UseRubyVersion@0 + - template: step--rust-install.yml + - bash: | + testing/get-swift.sh + echo '##vso[task.prependpath]/tmp/swift/usr/bin' + displayName: install swift diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py index 5f9f5c39..701afccb 100644 --- a/pre_commit/commands/install_uninstall.py +++ b/pre_commit/commands/install_uninstall.py @@ -5,6 +5,7 @@ import io import itertools import logging import os.path +import shutil import sys from pre_commit import git @@ -84,7 +85,7 @@ def install( # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): - os.rename(hook_path, legacy_path) + shutil.move(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index cfa62ee2..d060e186 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -190,7 +190,7 @@ def _compute_cols(hooks, verbose): def _all_filenames(args): if args.origin and args.source: return git.get_changed_files(args.origin, args.source) - elif args.hook_stage == 'commit-msg': + elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}: return (args.commit_msg_filename,) elif args.files: return args.files diff --git a/pre_commit/constants.py b/pre_commit/constants.py index 996480a9..307b09a4 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -21,6 +21,6 @@ LOCAL_REPO_VERSION = '1' VERSION = importlib_metadata.version('pre_commit') # `manual` is not invoked by any installed git hook. See #719 -STAGES = ('commit', 'commit-msg', 'manual', 'push') +STAGES = ('commit', 'prepare-commit-msg', 'commit-msg', 'manual', 'push') DEFAULT = 'default' diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index aac1c591..cd3b7b54 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -23,7 +23,7 @@ def _envdir(prefix, version): return prefix.path(directory) -def get_env_patch(venv): +def get_env_patch(venv): # pragma: windows no cover if sys.platform == 'cygwin': # pragma: no cover _, win_venv, _ = cmd_output('cygpath', '-w', venv) install_prefix = r'{}\bin'.format(win_venv.strip()) @@ -41,12 +41,14 @@ def get_env_patch(venv): @contextlib.contextmanager -def in_env(prefix, language_version): +def in_env(prefix, language_version): # pragma: windows no cover with envcontext(get_env_patch(_envdir(prefix, language_version))): yield -def install_environment(prefix, version, additional_dependencies): +def install_environment( + prefix, version, additional_dependencies, +): # pragma: windows no cover additional_dependencies = tuple(additional_dependencies) assert prefix.exists('package.json') envdir = _envdir(prefix, version) @@ -72,6 +74,6 @@ def install_environment(prefix, version, additional_dependencies): ) -def run_hook(hook, file_args): +def run_hook(hook, file_args): # pragma: windows no cover with in_env(hook.prefix, hook.language_version): return helpers.run_xargs(hook, helpers.to_cmd(hook), file_args) diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 86f5368c..2897d0ea 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -109,15 +109,15 @@ def norm_version(version): if _sys_executable_matches(version): return sys.executable + version_exec = _find_by_py_launcher(version) + if version_exec: + return version_exec + # Try looking up by name version_exec = find_executable(version) if version_exec and version_exec != version: return version_exec - version_exec = _find_by_py_launcher(version) - if version_exec: - return version_exec - # If it is in the form pythonx.x search in the default # place on windows if version.startswith('python'): diff --git a/pre_commit/main.py b/pre_commit/main.py index a935cf1c..aa7ff2a7 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -52,7 +52,9 @@ def _add_config_option(parser): def _add_hook_type_option(parser): parser.add_argument( - '-t', '--hook-type', choices=('pre-commit', 'pre-push', 'commit-msg'), + '-t', '--hook-type', choices=( + 'pre-commit', 'pre-push', 'prepare-commit-msg', 'commit-msg', + ), default='pre-commit', ) diff --git a/pre_commit/resources/hook-tmpl b/pre_commit/resources/hook-tmpl index 76123d3c..19d0e726 100755 --- a/pre_commit/resources/hook-tmpl +++ b/pre_commit/resources/hook-tmpl @@ -161,6 +161,7 @@ def _pre_push(stdin): def _opts(stdin): fns = { + 'prepare-commit-msg': lambda _: ('--commit-msg-filename', sys.argv[1]), 'commit-msg': lambda _: ('--commit-msg-filename', sys.argv[1]), 'pre-commit': lambda _: (), 'pre-push': _pre_push, diff --git a/pre_commit/store.py b/pre_commit/store.py index 93a9cab3..d1d432dc 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -138,7 +138,7 @@ class Store(object): def _complete_clone(self, ref, git_cmd): """Perform a complete clone of a repository and its submodules """ - git_cmd('fetch', 'origin') + git_cmd('fetch', 'origin', '--tags') git_cmd('checkout', ref) git_cmd('submodule', 'update', '--init', '--recursive') diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index f32cb32c..936a5bef 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -25,7 +25,7 @@ def _environ_size(_env=None): def _get_platform_max_length(): # pragma: no cover (platform specific) if os.name == 'posix': maximum = os.sysconf(str('SC_ARG_MAX')) - 2048 - _environ_size() - maximum = min(maximum, 2 ** 17) + maximum = max(min(maximum, 2 ** 17), 2 ** 12) return maximum elif os.name == 'nt': return 2 ** 15 - 2048 # UNICODE_STRING max - headroom diff --git a/setup.cfg b/setup.cfg index 178e492e..0e4cf7de 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 1.14.4 +version = 1.15.2 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/util.py b/testing/util.py index 15696730..d82612fa 100644 --- a/testing/util.py +++ b/testing/util.py @@ -30,8 +30,8 @@ def cmd_output_mocked_pre_commit_home(*args, **kwargs): skipif_cant_run_docker = pytest.mark.skipif( - docker_is_running() is False, - reason='Docker isn\'t running or can\'t be accessed', + os.name == 'nt' or not docker_is_running(), + reason="Docker isn't running or can't be accessed", ) skipif_cant_run_swift = pytest.mark.skipif( @@ -67,8 +67,8 @@ xfailif_broken_deep_listdir = pytest.mark.xfail( def platform_supports_pcre(): - output = cmd_output(GREP, '-P', "name='pre", 'setup.py', retcode=None) - return output[0] == 0 and "name='pre_commit'," in output[1] + output = cmd_output(GREP, '-P', "Don't", 'CHANGELOG.md', retcode=None) + return output[0] == 0 and "Don't use readlink -f" in output[1] xfailif_no_pcre_support = pytest.mark.xfail( diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index c19aaa44..3bb0a3ea 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -325,6 +325,16 @@ def test_install_existing_hooks_no_overwrite(tempdir_factory, store): assert NORMAL_PRE_COMMIT_RUN.match(output[len('legacy hook\n'):]) +def test_legacy_overwriting_legacy_hook(tempdir_factory, store): + path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') + with cwd(path): + _write_legacy_hook(path) + assert install(C.CONFIG_FILE, store) == 0 + _write_legacy_hook(path) + # this previously crashed on windows. See #1010 + assert install(C.CONFIG_FILE, store) == 0 + + def test_install_existing_hook_no_overwrite_idempotent(tempdir_factory, store): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): @@ -655,7 +665,65 @@ def test_commit_msg_legacy(commit_msg_repo, tempdir_factory, store): assert second_line.startswith('Must have "Signed off by:"...') -def test_install_disallow_mising_config(tempdir_factory, store): +def test_prepare_commit_msg_integration_failing( + failing_prepare_commit_msg_repo, tempdir_factory, store, +): + install(C.CONFIG_FILE, store, hook_type='prepare-commit-msg') + retc, out = _get_commit_output(tempdir_factory) + assert retc == 1 + assert out.startswith('Add "Signed off by:"...') + assert out.strip().endswith('...Failed') + + +def test_prepare_commit_msg_integration_passing( + prepare_commit_msg_repo, tempdir_factory, store, +): + install(C.CONFIG_FILE, store, hook_type='prepare-commit-msg') + msg = 'Hi' + retc, out = _get_commit_output(tempdir_factory, msg=msg) + assert retc == 0 + first_line = out.splitlines()[0] + assert first_line.startswith('Add "Signed off by:"...') + assert first_line.endswith('...Passed') + commit_msg_path = os.path.join( + prepare_commit_msg_repo, '.git/COMMIT_EDITMSG', + ) + with io.open(commit_msg_path) as f: + assert 'Signed off by: ' in f.read() + + +def test_prepare_commit_msg_legacy( + prepare_commit_msg_repo, tempdir_factory, store, +): + hook_path = os.path.join( + prepare_commit_msg_repo, '.git/hooks/prepare-commit-msg', + ) + mkdirp(os.path.dirname(hook_path)) + with io.open(hook_path, 'w') as hook_file: + hook_file.write( + '#!/usr/bin/env bash\n' + 'set -eu\n' + 'test -e "$1"\n' + 'echo legacy\n', + ) + make_executable(hook_path) + + install(C.CONFIG_FILE, store, hook_type='prepare-commit-msg') + + msg = 'Hi' + retc, out = _get_commit_output(tempdir_factory, msg=msg) + assert retc == 0 + first_line, second_line = out.splitlines()[:2] + assert first_line == 'legacy' + assert second_line.startswith('Add "Signed off by:"...') + commit_msg_path = os.path.join( + prepare_commit_msg_repo, '.git/COMMIT_EDITMSG', + ) + with io.open(commit_msg_path) as f: + assert 'Signed off by: ' in f.read() + + +def test_install_disallow_missing_config(tempdir_factory, store): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): remove_config_from_repo(path) @@ -668,7 +736,7 @@ def test_install_disallow_mising_config(tempdir_factory, store): assert ret == 1 -def test_install_allow_mising_config(tempdir_factory, store): +def test_install_allow_missing_config(tempdir_factory, store): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): remove_config_from_repo(path) diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 11a8eea1..b465cae6 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -557,7 +557,12 @@ def test_stages(cap_out, store, repo_with_passing_hook): 'language': 'pygrep', 'stages': [stage], } - for i, stage in enumerate(('commit', 'push', 'manual'), 1) + for i, stage in enumerate( + ( + 'commit', 'push', 'manual', 'prepare-commit-msg', + 'commit-msg', + ), 1, + ) ], } add_config_to_repo(repo_with_passing_hook, config) @@ -575,6 +580,8 @@ def test_stages(cap_out, store, repo_with_passing_hook): assert _run_for_stage('commit').startswith(b'hook 1...') assert _run_for_stage('push').startswith(b'hook 2...') assert _run_for_stage('manual').startswith(b'hook 3...') + assert _run_for_stage('prepare-commit-msg').startswith(b'hook 4...') + assert _run_for_stage('commit-msg').startswith(b'hook 5...') def test_commit_msg_hook(cap_out, store, commit_msg_repo): @@ -593,6 +600,25 @@ def test_commit_msg_hook(cap_out, store, commit_msg_repo): ) +def test_prepare_commit_msg_hook(cap_out, store, prepare_commit_msg_repo): + filename = '.git/COMMIT_EDITMSG' + with io.open(filename, 'w') as f: + f.write('This is the commit message') + + _test_run( + cap_out, + store, + prepare_commit_msg_repo, + {'hook_stage': 'prepare-commit-msg', 'commit_msg_filename': filename}, + expected_outputs=[b'Add "Signed off by:"', b'Passed'], + expected_ret=0, + stage=False, + ) + + with io.open(filename) as f: + assert 'Signed off by: ' in f.read() + + def test_local_hook_passes(cap_out, store, repo_with_passing_hook): config = { 'repo': 'local', diff --git a/tests/conftest.py b/tests/conftest.py index 50ad76ed..23ff7460 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,7 @@ from pre_commit import output from pre_commit.logging_handler import logging_handler from pre_commit.store import Store from pre_commit.util import cmd_output +from pre_commit.util import make_executable from testing.fixtures import git_dir from testing.fixtures import make_consuming_repo from testing.fixtures import write_config @@ -134,6 +135,54 @@ def commit_msg_repo(tempdir_factory): yield path +@pytest.fixture +def prepare_commit_msg_repo(tempdir_factory): + path = git_dir(tempdir_factory) + script_name = 'add_sign_off.sh' + config = { + 'repo': 'local', + 'hooks': [{ + 'id': 'add-signoff', + 'name': 'Add "Signed off by:"', + 'entry': './{}'.format(script_name), + 'language': 'script', + 'stages': ['prepare-commit-msg'], + }], + } + write_config(path, config) + with cwd(path): + with io.open(script_name, 'w') as script_file: + script_file.write( + '#!/usr/bin/env bash\n' + 'set -eu\n' + 'echo "\nSigned off by: " >> "$1"\n', + ) + make_executable(script_name) + cmd_output('git', 'add', '.') + git_commit(msg=prepare_commit_msg_repo.__name__) + yield path + + +@pytest.fixture +def failing_prepare_commit_msg_repo(tempdir_factory): + path = git_dir(tempdir_factory) + config = { + 'repo': 'local', + 'hooks': [{ + 'id': 'add-signoff', + 'name': 'Add "Signed off by:"', + 'entry': 'bash -c "exit 1"', + 'language': 'system', + 'stages': ['prepare-commit-msg'], + }], + } + write_config(path, config) + with cwd(path): + cmd_output('git', 'add', '.') + git_commit(msg=failing_prepare_commit_msg_repo.__name__) + yield path + + @pytest.fixture(autouse=True, scope='session') def dont_write_to_home_directory(): """pre_commit.store.Store will by default write to the home directory diff --git a/tests/repository_test.py b/tests/repository_test.py index d8bfde30..a2a9bb57 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -824,7 +824,9 @@ def test_manifest_hooks(tempdir_factory, store): name='Bash hook', pass_filenames=True, require_serial=False, - stages=('commit', 'commit-msg', 'manual', 'push'), + stages=( + 'commit', 'prepare-commit-msg', 'commit-msg', 'manual', 'push', + ), types=['file'], verbose=False, ) diff --git a/tests/store_test.py b/tests/store_test.py index 66217588..1833dee7 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -13,6 +13,7 @@ from pre_commit import git from pre_commit.store import _get_default_directory from pre_commit.store import Store from pre_commit.util import CalledProcessError +from pre_commit.util import cmd_output from testing.fixtures import git_dir from testing.util import cwd from testing.util import git_commit @@ -147,6 +148,20 @@ def test_clone_shallow_failure_fallback_to_complete( assert store.select_all_repos() == [(path, rev, ret)] +def test_clone_tag_not_on_mainline(store, tempdir_factory): + path = git_dir(tempdir_factory) + with cwd(path): + git_commit() + cmd_output('git', 'checkout', 'master', '-b', 'branch') + git_commit() + cmd_output('git', 'tag', 'v1') + cmd_output('git', 'checkout', 'master') + cmd_output('git', 'branch', '-D', 'branch') + + # previously crashed on unreachable refs + store.clone(path, 'v1') + + def test_create_when_directory_exists_but_not_db(store): # In versions <= 0.3.5, there was no sqlite db causing a need for # backward compatibility diff --git a/tests/xargs_test.py b/tests/xargs_test.py index 71f5454c..d2d7d7b3 100644 --- a/tests/xargs_test.py +++ b/tests/xargs_test.py @@ -145,7 +145,7 @@ def test_argument_too_long(): def test_xargs_smoke(): ret, out, err = xargs.xargs(('echo',), ('hello', 'world')) assert ret == 0 - assert out == b'hello world\n' + assert out.replace(b'\r\n', b'\n') == b'hello world\n' assert err == b'' diff --git a/tox.ini b/tox.ini index f63c3ce5..0ee1611f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,20 +1,19 @@ [tox] -project = pre_commit -# These should match the travis env list -envlist = py27,py36,py37,pypy +envlist = py27,py36,py37,pypy,pypy3,pre-commit [testenv] deps = -rrequirements-dev.txt -passenv = GOROOT HOME HOMEPATH PROGRAMDATA TERM +passenv = HOME LOCALAPPDATA commands = coverage erase coverage run -m pytest {posargs:tests} coverage report --fail-under 100 - pre-commit run --all-files + pre-commit install -[testenv:venv] -envdir = venv-{[tox]project} -commands = +[testenv:pre-commit] +skip_install = true +deps = pre-commit +commands = pre-commit run --all-files --show-diff-on-failure [pep8] ignore = E265,E501,W504