From 9cee71b5dfd75ecf70f6dc58b299836096ce4f4f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 12 Mar 2017 18:01:29 -0700 Subject: [PATCH 001/114] Add pyupgrade --- .pre-commit-config.yaml | 4 ++++ pre_commit_hooks/autopep8_wrapper.py | 2 +- pre_commit_hooks/check_added_large_files.py | 6 +++--- pre_commit_hooks/check_ast.py | 4 ++-- pre_commit_hooks/check_byte_order_marker.py | 2 +- pre_commit_hooks/check_case_conflict.py | 4 ++-- pre_commit_hooks/check_docstring_first.py | 8 ++++---- pre_commit_hooks/check_json.py | 2 +- pre_commit_hooks/check_symlinks.py | 2 +- pre_commit_hooks/check_xml.py | 2 +- pre_commit_hooks/debug_statement_hook.py | 6 +++--- pre_commit_hooks/detect_private_key.py | 2 +- pre_commit_hooks/end_of_file_fixer.py | 2 +- pre_commit_hooks/pretty_format_json.py | 6 +++--- pre_commit_hooks/requirements_txt_fixer.py | 2 +- pre_commit_hooks/string_fixer.py | 2 +- pre_commit_hooks/tests_should_end_in_test.py | 2 +- pre_commit_hooks/trailing_whitespace_fixer.py | 2 +- tests/readme_test.py | 2 +- 19 files changed, 33 insertions(+), 29 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3bb7a4..ccfdc4b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,3 +22,7 @@ hooks: - id: reorder-python-imports language_version: python2.7 +- repo: https://github.com/asottile/pyupgrade + sha: v1.0.0 + hooks: + - id: pyupgrade diff --git a/pre_commit_hooks/autopep8_wrapper.py b/pre_commit_hooks/autopep8_wrapper.py index f6f55fb..52aaebe 100644 --- a/pre_commit_hooks/autopep8_wrapper.py +++ b/pre_commit_hooks/autopep8_wrapper.py @@ -17,7 +17,7 @@ def main(argv=None): original_contents = io.open(filename).read() new_contents = autopep8.fix_code(original_contents, args) if original_contents != new_contents: - print('Fixing {0}'.format(filename)) + print('Fixing {}'.format(filename)) retv = 1 with io.open(filename, 'w') as output_file: output_file.write(new_contents) diff --git a/pre_commit_hooks/check_added_large_files.py b/pre_commit_hooks/check_added_large_files.py index 5ef7f22..8d5f4c4 100644 --- a/pre_commit_hooks/check_added_large_files.py +++ b/pre_commit_hooks/check_added_large_files.py @@ -26,10 +26,10 @@ def lfs_files(): assert mode in ('A', 'R') return filepart if mode == 'A' else filepart.split(' -> ')[1] - return set( + return { to_file_part(mode, filepart) for mode, filepart in modes_and_fileparts if mode in ('A', 'R') - ) + } def find_large_added_files(filenames, maxkb): @@ -41,7 +41,7 @@ def find_large_added_files(filenames, maxkb): for filename in filenames: kb = int(math.ceil(os.stat(filename).st_size / 1024)) if kb > maxkb: - print('{0} ({1} KB) exceeds {2} KB.'.format(filename, kb, maxkb)) + print('{} ({} KB) exceeds {} KB.'.format(filename, kb, maxkb)) retv = 1 return retv diff --git a/pre_commit_hooks/check_ast.py b/pre_commit_hooks/check_ast.py index c993e6a..169e077 100644 --- a/pre_commit_hooks/check_ast.py +++ b/pre_commit_hooks/check_ast.py @@ -22,10 +22,10 @@ def check_ast(argv=None): try: ast.parse(open(filename, 'rb').read(), filename=filename) except SyntaxError: - print('{0}: failed parsing with {1}:'.format( + print('{}: failed parsing with {}:'.format( filename, interpreter, )) - print('\n{0}'.format( + print('\n{}'.format( ' ' + traceback.format_exc().replace('\n', '\n ') )) retval = 1 diff --git a/pre_commit_hooks/check_byte_order_marker.py b/pre_commit_hooks/check_byte_order_marker.py index 274f949..1541b30 100644 --- a/pre_commit_hooks/check_byte_order_marker.py +++ b/pre_commit_hooks/check_byte_order_marker.py @@ -16,7 +16,7 @@ def main(argv=None): with open(filename, 'rb') as f: if f.read(3) == b'\xef\xbb\xbf': retv = 1 - print('{0}: Has a byte-order marker'.format(filename)) + print('{}: Has a byte-order marker'.format(filename)) return retv diff --git a/pre_commit_hooks/check_case_conflict.py b/pre_commit_hooks/check_case_conflict.py index dd4ad86..3d6cf74 100644 --- a/pre_commit_hooks/check_case_conflict.py +++ b/pre_commit_hooks/check_case_conflict.py @@ -9,7 +9,7 @@ from pre_commit_hooks.util import cmd_output def lower_set(iterable): - return set(x.lower() for x in iterable) + return {x.lower() for x in iterable} def find_conflicting_filenames(filenames): @@ -35,7 +35,7 @@ def find_conflicting_filenames(filenames): if x.lower() in conflicts ] for filename in sorted(conflicting_files): - print('Case-insensitivity conflict found: {0}'.format(filename)) + print('Case-insensitivity conflict found: {}'.format(filename)) retv = 1 return retv diff --git a/pre_commit_hooks/check_docstring_first.py b/pre_commit_hooks/check_docstring_first.py index da5425d..8e658a1 100644 --- a/pre_commit_hooks/check_docstring_first.py +++ b/pre_commit_hooks/check_docstring_first.py @@ -27,16 +27,16 @@ def check_docstring_first(src, filename=''): if tok_type == tokenize.STRING and scol == 0: if found_docstring_line is not None: print( - '{0}:{1} Multiple module docstrings ' - '(first docstring on line {2}).'.format( + '{}:{} Multiple module docstrings ' + '(first docstring on line {}).'.format( filename, sline, found_docstring_line, ) ) return 1 elif found_code_line is not None: print( - '{0}:{1} Module docstring appears after code ' - '(code seen on line {2}).'.format( + '{}:{} Module docstring appears after code ' + '(code seen on line {}).'.format( filename, sline, found_code_line, ) ) diff --git a/pre_commit_hooks/check_json.py b/pre_commit_hooks/check_json.py index 688f719..e1578ff 100644 --- a/pre_commit_hooks/check_json.py +++ b/pre_commit_hooks/check_json.py @@ -16,7 +16,7 @@ def check_json(argv=None): try: simplejson.load(open(filename)) except (simplejson.JSONDecodeError, UnicodeDecodeError) as exc: - print('{0}: Failed to json decode ({1})'.format(filename, exc)) + print('{}: Failed to json decode ({})'.format(filename, exc)) retval = 1 return retval diff --git a/pre_commit_hooks/check_symlinks.py b/pre_commit_hooks/check_symlinks.py index 713dd83..fd80089 100644 --- a/pre_commit_hooks/check_symlinks.py +++ b/pre_commit_hooks/check_symlinks.py @@ -19,7 +19,7 @@ def check_symlinks(argv=None): os.path.islink(filename) and not os.path.exists(filename) ): # pragma: no cover (symlink support required) - print('{0}: Broken symlink'.format(filename)) + print('{}: Broken symlink'.format(filename)) retv = 1 return retv diff --git a/pre_commit_hooks/check_xml.py b/pre_commit_hooks/check_xml.py index 926f6e9..a4c11a5 100644 --- a/pre_commit_hooks/check_xml.py +++ b/pre_commit_hooks/check_xml.py @@ -19,7 +19,7 @@ def check_xml(argv=None): with io.open(filename, 'rb') as xml_file: xml.sax.parse(xml_file, xml.sax.ContentHandler()) except xml.sax.SAXException as exc: - print('{0}: Failed to xml parse ({1})'.format(filename, exc)) + print('{}: Failed to xml parse ({})'.format(filename, exc)) retval = 1 return retval diff --git a/pre_commit_hooks/debug_statement_hook.py b/pre_commit_hooks/debug_statement_hook.py index 52fe72e..902198f 100644 --- a/pre_commit_hooks/debug_statement_hook.py +++ b/pre_commit_hooks/debug_statement_hook.py @@ -7,7 +7,7 @@ import collections import traceback -DEBUG_STATEMENTS = set(['pdb', 'ipdb', 'pudb', 'q', 'rdb']) +DEBUG_STATEMENTS = {'pdb', 'ipdb', 'pudb', 'q', 'rdb'} DebugStatement = collections.namedtuple( @@ -37,7 +37,7 @@ def check_file_for_debug_statements(filename): try: ast_obj = ast.parse(open(filename).read(), filename=filename) except SyntaxError: - print('{0} - Could not parse ast'.format(filename)) + print('{} - Could not parse ast'.format(filename)) print() print('\t' + traceback.format_exc().replace('\n', '\n\t')) print() @@ -47,7 +47,7 @@ def check_file_for_debug_statements(filename): if visitor.debug_import_statements: for debug_statement in visitor.debug_import_statements: print( - '{0}:{1}:{2} - {3} imported'.format( + '{}:{}:{} - {} imported'.format( filename, debug_statement.line, debug_statement.col, diff --git a/pre_commit_hooks/detect_private_key.py b/pre_commit_hooks/detect_private_key.py index d187364..feb2208 100644 --- a/pre_commit_hooks/detect_private_key.py +++ b/pre_commit_hooks/detect_private_key.py @@ -26,7 +26,7 @@ def detect_private_key(argv=None): if private_key_files: for private_key_file in private_key_files: - print('Private key found: {0}'.format(private_key_file)) + print('Private key found: {}'.format(private_key_file)) return 1 else: return 0 diff --git a/pre_commit_hooks/end_of_file_fixer.py b/pre_commit_hooks/end_of_file_fixer.py index 3349d83..4fe82b7 100644 --- a/pre_commit_hooks/end_of_file_fixer.py +++ b/pre_commit_hooks/end_of_file_fixer.py @@ -58,7 +58,7 @@ def end_of_file_fixer(argv=None): with open(filename, 'rb+') as file_obj: ret_for_file = fix_file(file_obj) if ret_for_file: - print('Fixing {0}'.format(filename)) + print('Fixing {}'.format(filename)) retv |= ret_for_file return retv diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py index 91dae8d..bf1ccb1 100644 --- a/pre_commit_hooks/pretty_format_json.py +++ b/pre_commit_hooks/pretty_format_json.py @@ -25,7 +25,7 @@ def _get_pretty_format(contents, indent, sort_keys=True, top_keys=[]): def _autofix(filename, new_contents): - print("Fixing file {0}".format(filename)) + print("Fixing file {}".format(filename)) with open(filename, 'w') as f: f.write(new_contents) @@ -100,7 +100,7 @@ def pretty_format_json(argv=None): ) if contents != pretty_contents: - print("File {0} is not pretty-formatted".format(json_file)) + print("File {} is not pretty-formatted".format(json_file)) if args.autofix: _autofix(json_file, pretty_contents) @@ -109,7 +109,7 @@ def pretty_format_json(argv=None): except simplejson.JSONDecodeError: print( - "Input File {0} is not a valid JSON, consider using check-json" + "Input File {} is not a valid JSON, consider using check-json" .format(json_file) ) return 1 diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py index 64b5e47..efa1906 100644 --- a/pre_commit_hooks/requirements_txt_fixer.py +++ b/pre_commit_hooks/requirements_txt_fixer.py @@ -84,7 +84,7 @@ def fix_requirements_txt(argv=None): ret_for_file = fix_requirements(file_obj) if ret_for_file: - print('Sorting {0}'.format(arg)) + print('Sorting {}'.format(arg)) retv |= ret_for_file diff --git a/pre_commit_hooks/string_fixer.py b/pre_commit_hooks/string_fixer.py index 9ef7a37..3523e8a 100644 --- a/pre_commit_hooks/string_fixer.py +++ b/pre_commit_hooks/string_fixer.py @@ -69,7 +69,7 @@ def main(argv=None): for filename in args.filenames: return_value = fix_strings(filename) if return_value != 0: - print('Fixing strings in {0}'.format(filename)) + print('Fixing strings in {}'.format(filename)) retv |= return_value return retv diff --git a/pre_commit_hooks/tests_should_end_in_test.py b/pre_commit_hooks/tests_should_end_in_test.py index 4bfc767..5f4cd08 100644 --- a/pre_commit_hooks/tests_should_end_in_test.py +++ b/pre_commit_hooks/tests_should_end_in_test.py @@ -26,7 +26,7 @@ def validate_files(argv=None): ): retcode = 1 print( - '{0} does not match pattern "{1}"'.format( + '{} does not match pattern "{}"'.format( filename, test_name_pattern ) ) diff --git a/pre_commit_hooks/trailing_whitespace_fixer.py b/pre_commit_hooks/trailing_whitespace_fixer.py index d23d58d..1ae15a9 100644 --- a/pre_commit_hooks/trailing_whitespace_fixer.py +++ b/pre_commit_hooks/trailing_whitespace_fixer.py @@ -67,7 +67,7 @@ def fix_trailing_whitespace(argv=None): for ext in md_exts: if any(c in ext[1:] for c in r'./\:'): parser.error( - "bad --markdown-linebreak-ext extension '{0}' (has . / \\ :)\n" + "bad --markdown-linebreak-ext extension '{}' (has . / \\ :)\n" " (probably filename; use '--markdown-linebreak-ext=EXT')" .format(ext) ) diff --git a/tests/readme_test.py b/tests/readme_test.py index d479d42..4d4c972 100644 --- a/tests/readme_test.py +++ b/tests/readme_test.py @@ -10,4 +10,4 @@ def test_readme_contains_all_hooks(): readme_contents = io.open('README.md').read() hooks = yaml.load(io.open('hooks.yaml').read()) for hook in hooks: - assert '`{0}`'.format(hook['id']) in readme_contents + assert '`{}`'.format(hook['id']) in readme_contents From ef7c425a3ef9c8bfcf2640a393aea363fa8c8172 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 13 Mar 2017 14:32:43 -0700 Subject: [PATCH 002/114] Require a newer version of autopep8 --- README.md | 3 ++- setup.py | 4 ++-- tests/autopep8_wrapper_test.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 031edfd..0650ca2 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ Add this to your `.pre-commit-config.yaml` - `autopep8-wrapper` - Runs autopep8 over python source. - Ignore PEP 8 violation types with `args: ['-i', '--ignore=E000,...']` or - through configuration of the `[pep8]` section in setup.cfg / tox.ini. + through configuration of the `[pycodestyle]` section in + setup.cfg / tox.ini. - `check-added-large-files` - Prevent giant files from being committed. - Specify what is "too large" with `args: ['--maxkb=123']` (default=500kB). - `check-ast` - Simply check whether files parse as valid python. diff --git a/setup.py b/setup.py index 68a8664..e82285d 100644 --- a/setup.py +++ b/setup.py @@ -24,9 +24,9 @@ setup( packages=find_packages(exclude=('tests*', 'testing*')), install_requires=[ - # quickfix to prevent pep8 conflicts + # quickfix to prevent pycodestyle conflicts 'flake8!=2.5.3', - 'autopep8>=1.1', + 'autopep8>=1.3', 'pyyaml', 'simplejson', 'six', diff --git a/tests/autopep8_wrapper_test.py b/tests/autopep8_wrapper_test.py index 5eb4df2..780752c 100644 --- a/tests/autopep8_wrapper_test.py +++ b/tests/autopep8_wrapper_test.py @@ -23,6 +23,6 @@ def test_main_failing(tmpdir, input_src, expected_ret, output_src): def test_respects_config_file(tmpdir): with tmpdir.as_cwd(): - tmpdir.join('setup.cfg').write('[pep8]\nignore=E221') + tmpdir.join('setup.cfg').write('[pycodestyle]\nignore=E221') tmpdir.join('test.py').write('print(1 + 2)\n') assert main(['test.py', '-i', '-v']) == 0 From c380d0b3e3b68c29f45faadb05476f12f268c0c0 Mon Sep 17 00:00:00 2001 From: Oliver Zhou Date: Wed, 15 Mar 2017 00:40:53 -0700 Subject: [PATCH 003/114] Handling default django test filename of "tests.py" --- pre_commit_hooks/tests_should_end_in_test.py | 2 +- tests/tests_should_end_in_test_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pre_commit_hooks/tests_should_end_in_test.py b/pre_commit_hooks/tests_should_end_in_test.py index 5f4cd08..5f9bbac 100644 --- a/pre_commit_hooks/tests_should_end_in_test.py +++ b/pre_commit_hooks/tests_should_end_in_test.py @@ -16,7 +16,7 @@ def validate_files(argv=None): args = parser.parse_args(argv) retcode = 0 - test_name_pattern = 'test_.*.py' if args.django else '.*_test.py' + test_name_pattern = 'test.*.py' if args.django else '.*_test.py' for filename in args.filenames: base = basename(filename) if ( diff --git a/tests/tests_should_end_in_test_test.py b/tests/tests_should_end_in_test_test.py index a7aaf52..dc686a5 100644 --- a/tests/tests_should_end_in_test_test.py +++ b/tests/tests_should_end_in_test_test.py @@ -12,7 +12,7 @@ def test_validate_files_one_fails(): def test_validate_files_django_all_pass(): - ret = validate_files(['--django', 'test_foo.py', 'test_bar.py', 'tests/test_baz.py']) + ret = validate_files(['--django', 'tests.py', 'test_foo.py', 'test_bar.py', 'tests/test_baz.py']) assert ret == 0 From 543c5c7e1a6696ead5c7225203696053a21416a6 Mon Sep 17 00:00:00 2001 From: John Hu Date: Thu, 16 Mar 2017 11:27:34 +0800 Subject: [PATCH 004/114] Add an option to disable ensure_ascii --- pre_commit_hooks/pretty_format_json.py | 33 ++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py index bf1ccb1..c1ee59d 100644 --- a/pre_commit_hooks/pretty_format_json.py +++ b/pre_commit_hooks/pretty_format_json.py @@ -1,13 +1,15 @@ from __future__ import print_function import argparse +import io import sys from collections import OrderedDict import simplejson +import six -def _get_pretty_format(contents, indent, sort_keys=True, top_keys=[]): +def _get_pretty_format(contents, indent, ensure_ascii=True, sort_keys=True, top_keys=[]): def pairs_first(pairs): before = [pair for pair in pairs if pair[0] in top_keys] before = sorted(before, key=lambda x: top_keys.index(x[0])) @@ -15,18 +17,19 @@ def _get_pretty_format(contents, indent, sort_keys=True, top_keys=[]): if sort_keys: after = sorted(after, key=lambda x: x[0]) return OrderedDict(before + after) - return simplejson.dumps( + return six.text_type(simplejson.dumps( simplejson.loads( contents, object_pairs_hook=pairs_first, ), - indent=indent - ) + "\n" # dumps don't end with a newline + indent=indent, + ensure_ascii=ensure_ascii + )) + "\n" # dumps don't end with a newline -def _autofix(filename, new_contents): +def _autofix(filename, new_contents, encoding=None): print("Fixing file {}".format(filename)) - with open(filename, 'w') as f: + with io.open(filename, 'w', encoding=encoding) as f: f.write(new_contents) @@ -69,6 +72,13 @@ def pretty_format_json(argv=None): default=' ', help='String used as delimiter for one indentation level', ) + parser.add_argument( + '--no-ensure-ascii', + action='store_true', + dest='no_ensure_ascii', + default=False, + help='Do NOT convert non-ASCII characters to Unicode escape sequences (\\uXXXX)', + ) parser.add_argument( '--no-sort-keys', action='store_true', @@ -90,20 +100,23 @@ def pretty_format_json(argv=None): status = 0 for json_file in args.filenames: - with open(json_file) as f: + with io.open(json_file, encoding='utf-8') as f: contents = f.read() try: pretty_contents = _get_pretty_format( - contents, args.indent, sort_keys=not args.no_sort_keys, - top_keys=args.top_keys + contents, args.indent, ensure_ascii=not args.no_ensure_ascii, + sort_keys=not args.no_sort_keys, top_keys=args.top_keys ) if contents != pretty_contents: print("File {} is not pretty-formatted".format(json_file)) if args.autofix: - _autofix(json_file, pretty_contents) + _autofix( + json_file, pretty_contents, + encoding='utf-8' if args.no_ensure_ascii else None + ) status = 1 From 10f8bd2f2264a373e5a5d7b043f8dd1ea326a81c Mon Sep 17 00:00:00 2001 From: ushuz Date: Thu, 16 Mar 2017 14:54:55 +0800 Subject: [PATCH 005/114] Add unit test for --no-ensure-ascii option --- testing/resources/non_ascii_pretty_formatted_json.json | 10 ++++++++++ tests/pretty_format_json_test.py | 8 ++++++++ 2 files changed, 18 insertions(+) create mode 100644 testing/resources/non_ascii_pretty_formatted_json.json diff --git a/testing/resources/non_ascii_pretty_formatted_json.json b/testing/resources/non_ascii_pretty_formatted_json.json new file mode 100644 index 0000000..05d0d00 --- /dev/null +++ b/testing/resources/non_ascii_pretty_formatted_json.json @@ -0,0 +1,10 @@ +{ + "alist": [ + 2, + 34, + 234 + ], + "blah": null, + "foo": "bar", + "non_ascii": "中文にほんご한국어" +} diff --git a/tests/pretty_format_json_test.py b/tests/pretty_format_json_test.py index 7bfc31f..62e37f1 100644 --- a/tests/pretty_format_json_test.py +++ b/tests/pretty_format_json_test.py @@ -20,6 +20,7 @@ def test_parse_indent(): @pytest.mark.parametrize(('filename', 'expected_retval'), ( ('not_pretty_formatted_json.json', 1), ('unsorted_pretty_formatted_json.json', 1), + ('non_ascii_pretty_formatted_json.json', 1), ('pretty_formatted_json.json', 0), )) def test_pretty_format_json(filename, expected_retval): @@ -30,6 +31,7 @@ def test_pretty_format_json(filename, expected_retval): @pytest.mark.parametrize(('filename', 'expected_retval'), ( ('not_pretty_formatted_json.json', 1), ('unsorted_pretty_formatted_json.json', 0), + ('non_ascii_pretty_formatted_json.json', 1), ('pretty_formatted_json.json', 0), )) def test_unsorted_pretty_format_json(filename, expected_retval): @@ -40,6 +42,7 @@ def test_unsorted_pretty_format_json(filename, expected_retval): @pytest.mark.parametrize(('filename', 'expected_retval'), ( ('not_pretty_formatted_json.json', 1), ('unsorted_pretty_formatted_json.json', 1), + ('non_ascii_pretty_formatted_json.json', 1), ('pretty_formatted_json.json', 1), ('tab_pretty_formatted_json.json', 0), )) @@ -48,6 +51,11 @@ def test_tab_pretty_format_json(filename, expected_retval): assert ret == expected_retval +def test_non_ascii_pretty_format_json(): + ret = pretty_format_json(['--no-ensure-ascii', get_resource_path('non_ascii_pretty_formatted_json.json')]) + assert ret == 0 + + def test_autofix_pretty_format_json(tmpdir): srcfile = tmpdir.join('to_be_json_formatted.json') shutil.copyfile( From b95dcad616c898a896cb4fede55dab2a7d46b989 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Mar 2017 08:24:58 -0700 Subject: [PATCH 006/114] Fixups --- .pre-commit-config.yaml | 2 +- pre_commit_hooks/pretty_format_json.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ccfdc4b..193b00e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v0.7.0 + sha: 4c3cc8f3edc3c673b3a4abe2054e64f21d26772c hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py index c1ee59d..76b8cfb 100644 --- a/pre_commit_hooks/pretty_format_json.py +++ b/pre_commit_hooks/pretty_format_json.py @@ -23,8 +23,8 @@ def _get_pretty_format(contents, indent, ensure_ascii=True, sort_keys=True, top_ object_pairs_hook=pairs_first, ), indent=indent, - ensure_ascii=ensure_ascii - )) + "\n" # dumps don't end with a newline + ensure_ascii=ensure_ascii, + )) + "\n" # dumps does not end with a newline def _autofix(filename, new_contents, encoding=None): @@ -106,7 +106,7 @@ def pretty_format_json(argv=None): try: pretty_contents = _get_pretty_format( contents, args.indent, ensure_ascii=not args.no_ensure_ascii, - sort_keys=not args.no_sort_keys, top_keys=args.top_keys + sort_keys=not args.no_sort_keys, top_keys=args.top_keys, ) if contents != pretty_contents: @@ -115,7 +115,7 @@ def pretty_format_json(argv=None): if args.autofix: _autofix( json_file, pretty_contents, - encoding='utf-8' if args.no_ensure_ascii else None + encoding='utf-8' if args.no_ensure_ascii else None, ) status = 1 From a8592669d958b27368e4621741c1f0cb5e538f36 Mon Sep 17 00:00:00 2001 From: Evan Felix Date: Mon, 20 Mar 2017 10:36:51 -0700 Subject: [PATCH 007/114] Add a no commit to specific branch hook. (#185) * add no commit code and config * add the code * remove version tweak * fix logic, remove newline * add Tests and cleanup testing issues * remove extraneous modules * cleanup some pep8 and flake issues * reorder imports * more fixes for syntax checking * code cleanup based off asottile comments * Use Contractions Properly, alphabatize new hook. * Adding support for branches with a slash in them. --- .pre-commit-hooks.yaml | 6 ++++ README.md | 2 ++ hooks.yaml | 6 ++++ pre_commit_hooks/no_commit_to_branch.py | 25 +++++++++++++ setup.py | 1 + tests/check_no_commit_to_branch_test.py | 47 +++++++++++++++++++++++++ 6 files changed, 87 insertions(+) create mode 100644 pre_commit_hooks/no_commit_to_branch.py create mode 100644 tests/check_no_commit_to_branch_test.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 0604585..bda3f76 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -129,6 +129,12 @@ entry: name-tests-test language: python files: tests/.+\.py$ +- id: no-commit-to-branch + name: "Don't commit to branch" + entry: no-commit-to-branch + language: python + files: .* + always_run: true - id: pyflakes name: Pyflakes (DEPRECATED, use flake8) description: This hook runs pyflakes. (This is deprecated, use flake8). diff --git a/README.md b/README.md index 031edfd..723136d 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ Add this to your `.pre-commit-config.yaml` - `forbid-new-submodules` - Prevent addition of new git submodules. - `name-tests-test` - Assert that files in tests/ end in `_test.py`. - Use `args: ['--django']` to match `test*.py` instead. +- `no-commit-to-branch` - Protect specific branches from direct checkins. + - Use `args: -b ` to set the branch. `master` is the default if no argument is set. - `pyflakes` - Run pyflakes on your python files. - `pretty-format-json` - Checks that all your JSON files are pretty. "Pretty" here means that keys are sorted and indented. You can configure this with diff --git a/hooks.yaml b/hooks.yaml index 0604585..bda3f76 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -129,6 +129,12 @@ entry: name-tests-test language: python files: tests/.+\.py$ +- id: no-commit-to-branch + name: "Don't commit to branch" + entry: no-commit-to-branch + language: python + files: .* + always_run: true - id: pyflakes name: Pyflakes (DEPRECATED, use flake8) description: This hook runs pyflakes. (This is deprecated, use flake8). diff --git a/pre_commit_hooks/no_commit_to_branch.py b/pre_commit_hooks/no_commit_to_branch.py new file mode 100644 index 0000000..4aa3535 --- /dev/null +++ b/pre_commit_hooks/no_commit_to_branch.py @@ -0,0 +1,25 @@ +from __future__ import print_function + +import argparse +import sys + +from pre_commit_hooks.util import cmd_output + + +def is_on_branch(protected): + branch = cmd_output('git', 'symbolic-ref', 'HEAD') + chunks = branch.strip().split('/') + return '/'.join(chunks[2:]) == protected + + +def main(argv=[]): + parser = argparse.ArgumentParser() + parser.add_argument( + '-b', '--branch', default='master', help='branch to disallow commits to') + args = parser.parse_args(argv) + + return int(is_on_branch(args.branch)) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/setup.py b/setup.py index 68a8664..9db61a4 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ setup( 'fix-encoding-pragma = pre_commit_hooks.fix_encoding_pragma:main', 'forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main', 'name-tests-test = pre_commit_hooks.tests_should_end_in_test:validate_files', + 'no-commit-to-branch = pre_commit_hooks.no_commit_to_branch:main', 'pretty-format-json = pre_commit_hooks.pretty_format_json:pretty_format_json', 'requirements-txt-fixer = pre_commit_hooks.requirements_txt_fixer:fix_requirements_txt', 'trailing-whitespace-fixer = pre_commit_hooks.trailing_whitespace_fixer:fix_trailing_whitespace', diff --git a/tests/check_no_commit_to_branch_test.py b/tests/check_no_commit_to_branch_test.py new file mode 100644 index 0000000..99af938 --- /dev/null +++ b/tests/check_no_commit_to_branch_test.py @@ -0,0 +1,47 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +from pre_commit_hooks.no_commit_to_branch import is_on_branch +from pre_commit_hooks.no_commit_to_branch import main +from pre_commit_hooks.util import cmd_output + + +def test_other_branch(temp_git_dir): + with temp_git_dir.as_cwd(): + cmd_output('git', 'checkout', '-b', 'anotherbranch') + assert is_on_branch('master') is False + + +def test_multi_branch(temp_git_dir): + with temp_git_dir.as_cwd(): + cmd_output('git', 'checkout', '-b', 'another/branch') + assert is_on_branch('master') is False + + +def test_multi_branch_fail(temp_git_dir): + with temp_git_dir.as_cwd(): + cmd_output('git', 'checkout', '-b', 'another/branch') + assert is_on_branch('another/branch') is True + + +def test_master_branch(temp_git_dir): + with temp_git_dir.as_cwd(): + assert is_on_branch('master') is True + + +def test_main_b_call(temp_git_dir): + with temp_git_dir.as_cwd(): + cmd_output('git', 'checkout', '-b', 'other') + assert main(['-b', 'other']) == 1 + + +def test_main_branch_call(temp_git_dir): + with temp_git_dir.as_cwd(): + cmd_output('git', 'checkout', '-b', 'other') + assert main(['--branch', 'other']) == 1 + + +def test_main_default_call(temp_git_dir): + with temp_git_dir.as_cwd(): + cmd_output('git', 'checkout', '-b', 'anotherbranch') + assert main() == 0 From dc50b7f09c9612624c97e7f11fa346937c37f444 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Mar 2017 10:42:48 -0700 Subject: [PATCH 008/114] Attempt to fix the json hook under test --- pre_commit_hooks/check_json.py | 8 ++++---- pre_commit_hooks/pretty_format_json.py | 11 ++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pre_commit_hooks/check_json.py b/pre_commit_hooks/check_json.py index e1578ff..b403f4b 100644 --- a/pre_commit_hooks/check_json.py +++ b/pre_commit_hooks/check_json.py @@ -1,10 +1,10 @@ from __future__ import print_function import argparse +import io +import json import sys -import simplejson - def check_json(argv=None): parser = argparse.ArgumentParser() @@ -14,8 +14,8 @@ def check_json(argv=None): retval = 0 for filename in args.filenames: try: - simplejson.load(open(filename)) - except (simplejson.JSONDecodeError, UnicodeDecodeError) as exc: + json.load(io.open(filename, encoding='UTF-8')) + except (ValueError, UnicodeDecodeError) as exc: print('{}: Failed to json decode ({})'.format(filename, exc)) retval = 1 return retval diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py index 76b8cfb..5e04230 100644 --- a/pre_commit_hooks/pretty_format_json.py +++ b/pre_commit_hooks/pretty_format_json.py @@ -27,9 +27,9 @@ def _get_pretty_format(contents, indent, ensure_ascii=True, sort_keys=True, top_ )) + "\n" # dumps does not end with a newline -def _autofix(filename, new_contents, encoding=None): +def _autofix(filename, new_contents): print("Fixing file {}".format(filename)) - with io.open(filename, 'w', encoding=encoding) as f: + with io.open(filename, 'w', encoding='UTF-8') as f: f.write(new_contents) @@ -100,7 +100,7 @@ def pretty_format_json(argv=None): status = 0 for json_file in args.filenames: - with io.open(json_file, encoding='utf-8') as f: + with io.open(json_file, encoding='UTF-8') as f: contents = f.read() try: @@ -113,10 +113,7 @@ def pretty_format_json(argv=None): print("File {} is not pretty-formatted".format(json_file)) if args.autofix: - _autofix( - json_file, pretty_contents, - encoding='utf-8' if args.no_ensure_ascii else None, - ) + _autofix(json_file, pretty_contents) status = 1 From c71b80b6ed4474d8c62354aa8526ec542af32f0c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Mar 2017 10:43:12 -0700 Subject: [PATCH 009/114] bump revision of pre-commit-hooks --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 193b00e..fed900a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ - repo: https://github.com/pre-commit/pre-commit-hooks - sha: 4c3cc8f3edc3c673b3a4abe2054e64f21d26772c + sha: dc50b7f09c9612624c97e7f11fa346937c37f444 hooks: - id: trailing-whitespace - id: end-of-file-fixer From 78818b90cd694c29333ba54d38f9e60b6359ccfc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 6 Jun 2017 14:26:36 -0700 Subject: [PATCH 010/114] v0.8.0 --- .pre-commit-config.yaml | 2 +- CHANGELOG | 7 +++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fed900a..be99f71 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ - repo: https://github.com/pre-commit/pre-commit-hooks - sha: dc50b7f09c9612624c97e7f11fa346937c37f444 + sha: v0.8.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG b/CHANGELOG index c489206..f547531 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +0.8.0 +===== +- Add flag allowing missing keys to `detect-aws-credentials` +- Handle django default `tests.py` in `name-tests-test` +- Add `--no-ensure-ascii` option to `pretty-format-json` +- Add `no-commit-to-branch` hook + 0.7.1 ===== - Don't false positive on files where trailing whitespace isn't changed. diff --git a/README.md b/README.md index 723136d..3b62234 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v0.7.1 # Use the ref you want to point at + sha: v0.8.0 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 9db61a4..4abb7a2 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='0.7.1', + version='0.8.0', author='Anthony Sottile', author_email='asottile@umich.edu', From 6076fd1b157f5679276da6c0b6dfd413d841387c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2017 10:39:07 -0700 Subject: [PATCH 011/114] Support rebase conflicts in check-merge-conflicts --- pre_commit_hooks/check_merge_conflict.py | 6 +++++- tests/check_merge_conflict_test.py | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pre_commit_hooks/check_merge_conflict.py b/pre_commit_hooks/check_merge_conflict.py index d986998..7d87efc 100644 --- a/pre_commit_hooks/check_merge_conflict.py +++ b/pre_commit_hooks/check_merge_conflict.py @@ -15,7 +15,11 @@ WARNING_MSG = 'Merge conflict string "{0}" found in {1}:{2}' def is_in_merge(): return ( os.path.exists(os.path.join('.git', 'MERGE_MSG')) and - os.path.exists(os.path.join('.git', 'MERGE_HEAD')) + ( + os.path.exists(os.path.join('.git', 'MERGE_HEAD')) or + os.path.exists(os.path.join('.git', 'rebase-apply')) or + os.path.exists(os.path.join('.git', 'rebase-merge')) + ) ) diff --git a/tests/check_merge_conflict_test.py b/tests/check_merge_conflict_test.py index f1528b2..5a2e82a 100644 --- a/tests/check_merge_conflict_test.py +++ b/tests/check_merge_conflict_test.py @@ -54,6 +54,14 @@ def f1_is_a_conflict_file(tmpdir): '=======\n' 'parent\n' '>>>>>>>' + ) or f1.startswith( + # .gitconfig with [pull] rebase = preserve causes a rebase which + # flips parent / child + '<<<<<<< HEAD\n' + 'parent\n' + '=======\n' + 'child\n' + '>>>>>>>' ) assert os.path.exists(os.path.join('.git', 'MERGE_MSG')) yield @@ -85,7 +93,7 @@ def repository_is_pending_merge(tmpdir): repo2_f2.write('child\n') cmd_output('git', 'add', '--', repo2_f2.strpath) cmd_output('git', 'commit', '--no-gpg-sign', '-m', 'clone commit2') - cmd_output('git', 'pull', '--no-commit') + cmd_output('git', 'pull', '--no-commit', '--no-rebase') # We should end up in a pending merge assert repo2_f1.read() == 'parent\n' assert repo2_f2.read() == 'child\n' From 9425c5d6b5b8c7abd62fff140a754af1452f1209 Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Fri, 23 Jun 2017 00:33:13 -0700 Subject: [PATCH 012/114] First commit of file-contents-sorter precommit hook --- .pre-commit-hooks.yaml | 6 +++ pre_commit_hooks/file_contents_sorter.py | 57 ++++++++++++++++++++++++ requirements-dev.txt | 1 + setup.py | 1 + tests/file_contents_sorter_test.py | 54 ++++++++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100644 pre_commit_hooks/file_contents_sorter.py create mode 100644 tests/file_contents_sorter_test.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index bda3f76..d501d50 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -105,6 +105,12 @@ entry: end-of-file-fixer language: python files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$ +- id: file-contents-sorter + name: File Contents Sorter + description: Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input in your .pre-commit-config.yaml file. + entry: file-contents-sorter + language: python + files: '' - id: fix-encoding-pragma name: Fix python encoding pragma language: python diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py new file mode 100644 index 0000000..06c6d3a --- /dev/null +++ b/pre_commit_hooks/file_contents_sorter.py @@ -0,0 +1,57 @@ +""" +A very simple pre-commit hook that, when passed one or more filenames +as arguments, will sort the lines in those files. + +An example use case for this: you have a deploy-whitelist.txt file +in a repo that contains a list of filenames that is used to specify +files to be included in a docker container. This file has one filename +per line. Various users are adding/removing lines from this file; using +this hook on that file should reduce the instances of git merge +conflicts and keep the file nicely ordered. +""" +from __future__ import print_function + +import argparse + +PASS = 0 +FAIL = 1 + + +def sort_file_contents(f): + before = [line for line in f] + after = sorted(before) + + before_string = b''.join(before) + after_string = b''.join(after) + + if before_string == after_string: + return PASS + else: + f.seek(0) + f.write(after_string) + f.truncate() + return FAIL + + +def parse_commandline_input(argv): + parser = argparse.ArgumentParser() + parser.add_argument('filenames', nargs='+', help='Files to sort') + args = parser.parse_args(argv) + return args + + +def main(argv=None): + args = parse_commandline_input(argv) + + retv = PASS + + for arg in args.filenames: + with open(arg, 'rb+') as file_obj: + ret_for_file = sort_file_contents(file_obj) + + if ret_for_file: + print('Sorting {}'.format(arg)) + + retv |= ret_for_file + + return retv diff --git a/requirements-dev.txt b/requirements-dev.txt index 2922ef5..4070e66 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,7 @@ coverage flake8 +ipdb mock pre-commit pytest diff --git a/setup.py b/setup.py index 4abb7a2..3f761f6 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,7 @@ setup( 'detect-private-key = pre_commit_hooks.detect_private_key:detect_private_key', 'double-quote-string-fixer = pre_commit_hooks.string_fixer:main', 'end-of-file-fixer = pre_commit_hooks.end_of_file_fixer:end_of_file_fixer', + 'file-contents-sorter = pre_commit_hooks.file_contents_sorter:main', 'fix-encoding-pragma = pre_commit_hooks.fix_encoding_pragma:main', 'forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main', 'name-tests-test = pre_commit_hooks.tests_should_end_in_test:validate_files', diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py new file mode 100644 index 0000000..a8fb4c8 --- /dev/null +++ b/tests/file_contents_sorter_test.py @@ -0,0 +1,54 @@ +from argparse import ArgumentError + +import pytest + +from pre_commit_hooks.file_contents_sorter import FAIL +from pre_commit_hooks.file_contents_sorter import main +from pre_commit_hooks.file_contents_sorter import parse_commandline_input +from pre_commit_hooks.file_contents_sorter import PASS +from pre_commit_hooks.file_contents_sorter import sort_file_contents + + +def _n(*strs): + return b'\n'.join(strs) + '\n' + + +# Input, expected return value, expected output +TESTS = ( + (b'', PASS, b''), + (_n('lonesome'), PASS, _n('lonesome')), + (b'missing_newline', PASS, b'missing_newline'), + (_n('alpha', 'beta'), PASS, _n('alpha', 'beta')), + (_n('beta', 'alpha'), FAIL, _n('alpha', 'beta')), + (_n('C', 'c'), PASS, _n('C', 'c')), + (_n('c', 'C'), FAIL, _n('C', 'c')), + (_n('mag ical ', ' tre vor'), FAIL, _n(' tre vor', 'mag ical ')), + (_n('@', '-', '_', '#'), FAIL, _n('#', '-', '@', '_')), +) + + +@pytest.mark.parametrize(('input_s', 'expected_retval', 'output'), TESTS) +def test_integration(input_s, expected_retval, output, tmpdir): + path = tmpdir.join('file.txt') + path.write_binary(input_s) + + output_retval = main([path.strpath]) + + assert path.read_binary() == output + assert output_retval == expected_retval + + +def test_parse_commandline_input_errors_without_args(): + with pytest.raises(SystemExit): + parse_commandline_input([]) + +@pytest.mark.parametrize( + ('filename_list'), + ( + ['filename1'], + ['filename1', 'filename2'], + ) +) +def test_parse_commandline_input_success(filename_list): + args = parse_commandline_input(filename_list) + assert args.filenames == filename_list \ No newline at end of file From 8b41c575db4377b500aba94a2918bbe74a163108 Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Fri, 23 Jun 2017 10:44:10 -0700 Subject: [PATCH 013/114] cp .pre-commit-hooks.yaml hooks.yaml --- .gitignore | 1 + hooks.yaml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 2626934..6fdf044 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.iml *.py[co] .*.sw[a-z] +.cache .coverage .idea .project diff --git a/hooks.yaml b/hooks.yaml index bda3f76..d501d50 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -105,6 +105,12 @@ entry: end-of-file-fixer language: python files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$ +- id: file-contents-sorter + name: File Contents Sorter + description: Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input in your .pre-commit-config.yaml file. + entry: file-contents-sorter + language: python + files: '' - id: fix-encoding-pragma name: Fix python encoding pragma language: python From 4af74511548948f9a45850c9142ee23661aef371 Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Fri, 23 Jun 2017 11:32:05 -0700 Subject: [PATCH 014/114] Update README.md about file-contents-sorter --- README.md | 1 + tests/file_contents_sorter_test.py | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3b62234..894bd83 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Add this to your `.pre-commit-config.yaml` with single quoted strings. - `end-of-file-fixer` - Makes sure files end in a newline and only a newline. - `fix-encoding-pragma` - Add `# -*- coding: utf-8 -*-` to the top of python files. +- `file-contents-sorter` - Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input to it. - To remove the coding pragma pass `--remove` (useful in a python3-only codebase) - `flake8` - Run flake8 on your python files. - `forbid-new-submodules` - Prevent addition of new git submodules. diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py index a8fb4c8..4e65629 100644 --- a/tests/file_contents_sorter_test.py +++ b/tests/file_contents_sorter_test.py @@ -1,29 +1,26 @@ -from argparse import ArgumentError - import pytest from pre_commit_hooks.file_contents_sorter import FAIL from pre_commit_hooks.file_contents_sorter import main from pre_commit_hooks.file_contents_sorter import parse_commandline_input from pre_commit_hooks.file_contents_sorter import PASS -from pre_commit_hooks.file_contents_sorter import sort_file_contents def _n(*strs): - return b'\n'.join(strs) + '\n' + return b'\n'.join(strs) + b'\n' # Input, expected return value, expected output TESTS = ( (b'', PASS, b''), - (_n('lonesome'), PASS, _n('lonesome')), + (_n(b'lonesome'), PASS, _n(b'lonesome')), (b'missing_newline', PASS, b'missing_newline'), - (_n('alpha', 'beta'), PASS, _n('alpha', 'beta')), - (_n('beta', 'alpha'), FAIL, _n('alpha', 'beta')), - (_n('C', 'c'), PASS, _n('C', 'c')), - (_n('c', 'C'), FAIL, _n('C', 'c')), - (_n('mag ical ', ' tre vor'), FAIL, _n(' tre vor', 'mag ical ')), - (_n('@', '-', '_', '#'), FAIL, _n('#', '-', '@', '_')), + (_n(b'alpha', b'beta'), PASS, _n(b'alpha', b'beta')), + (_n(b'beta', b'alpha'), FAIL, _n(b'alpha', b'beta')), + (_n(b'C', b'c'), PASS, _n(b'C', b'c')), + (_n(b'c', b'C'), FAIL, _n(b'C', b'c')), + (_n(b'mag ical ', b' tre vor'), FAIL, _n(b' tre vor', b'mag ical ')), + (_n(b'@', b'-', b'_', b'#'), FAIL, _n(b'#', b'-', b'@', b'_')), ) @@ -42,13 +39,14 @@ def test_parse_commandline_input_errors_without_args(): with pytest.raises(SystemExit): parse_commandline_input([]) + @pytest.mark.parametrize( - ('filename_list'), + ('filename_list'), ( - ['filename1'], + ['filename1'], ['filename1', 'filename2'], ) ) def test_parse_commandline_input_success(filename_list): args = parse_commandline_input(filename_list) - assert args.filenames == filename_list \ No newline at end of file + assert args.filenames == filename_list From b941d0e6dfb07cfd278fe958bcd278dc3936616b Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Fri, 23 Jun 2017 14:58:24 -0700 Subject: [PATCH 015/114] Respond to review feedback --- .pre-commit-hooks.yaml | 2 +- pre_commit_hooks/file_contents_sorter.py | 2 +- requirements-dev.txt | 1 - tests/file_contents_sorter_test.py | 18 +++++++----------- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index d501d50..eea7bed 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -110,7 +110,7 @@ description: Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input in your .pre-commit-config.yaml file. entry: file-contents-sorter language: python - files: '' + files: '^$' - id: fix-encoding-pragma name: Fix python encoding pragma language: python diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py index 06c6d3a..e01eb8c 100644 --- a/pre_commit_hooks/file_contents_sorter.py +++ b/pre_commit_hooks/file_contents_sorter.py @@ -18,7 +18,7 @@ FAIL = 1 def sort_file_contents(f): - before = [line for line in f] + before = list(f) after = sorted(before) before_string = b''.join(before) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4070e66..2922ef5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,6 @@ coverage flake8 -ipdb mock pre-commit pytest diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py index 4e65629..e8f1ea8 100644 --- a/tests/file_contents_sorter_test.py +++ b/tests/file_contents_sorter_test.py @@ -6,21 +6,17 @@ from pre_commit_hooks.file_contents_sorter import parse_commandline_input from pre_commit_hooks.file_contents_sorter import PASS -def _n(*strs): - return b'\n'.join(strs) + b'\n' - - # Input, expected return value, expected output TESTS = ( (b'', PASS, b''), - (_n(b'lonesome'), PASS, _n(b'lonesome')), + (b'lonesome\n', PASS, b'lonesome\n'), (b'missing_newline', PASS, b'missing_newline'), - (_n(b'alpha', b'beta'), PASS, _n(b'alpha', b'beta')), - (_n(b'beta', b'alpha'), FAIL, _n(b'alpha', b'beta')), - (_n(b'C', b'c'), PASS, _n(b'C', b'c')), - (_n(b'c', b'C'), FAIL, _n(b'C', b'c')), - (_n(b'mag ical ', b' tre vor'), FAIL, _n(b' tre vor', b'mag ical ')), - (_n(b'@', b'-', b'_', b'#'), FAIL, _n(b'#', b'-', b'@', b'_')), + (b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'), + (b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'), + (b'C\nc\n', PASS, b'C\nc\n'), + (b'c\nC\n', FAIL, b'C\nc\n'), + (b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'), + (b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'), ) From 05d9c8c8051446dc14aec45a859f39ae22c01bdb Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Fri, 23 Jun 2017 15:10:10 -0700 Subject: [PATCH 016/114] Make tests pass --- hooks.yaml | 2 +- tests/file_contents_sorter_test.py | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/hooks.yaml b/hooks.yaml index d501d50..eea7bed 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -110,7 +110,7 @@ description: Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input in your .pre-commit-config.yaml file. entry: file-contents-sorter language: python - files: '' + files: '^$' - id: fix-encoding-pragma name: Fix python encoding pragma language: python diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py index e8f1ea8..7b3d098 100644 --- a/tests/file_contents_sorter_test.py +++ b/tests/file_contents_sorter_test.py @@ -2,7 +2,6 @@ import pytest from pre_commit_hooks.file_contents_sorter import FAIL from pre_commit_hooks.file_contents_sorter import main -from pre_commit_hooks.file_contents_sorter import parse_commandline_input from pre_commit_hooks.file_contents_sorter import PASS @@ -29,20 +28,3 @@ def test_integration(input_s, expected_retval, output, tmpdir): assert path.read_binary() == output assert output_retval == expected_retval - - -def test_parse_commandline_input_errors_without_args(): - with pytest.raises(SystemExit): - parse_commandline_input([]) - - -@pytest.mark.parametrize( - ('filename_list'), - ( - ['filename1'], - ['filename1', 'filename2'], - ) -) -def test_parse_commandline_input_success(filename_list): - args = parse_commandline_input(filename_list) - assert args.filenames == filename_list From b6eff3d39e2049e67afe019788c4082ba5369921 Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Fri, 23 Jun 2017 16:26:00 -0700 Subject: [PATCH 017/114] Add sort-simple-yaml hook (originally private hook from yelp_pre_commit_hooks) --- .gitignore | 1 + .pre-commit-hooks.yaml | 6 ++ README.md | 1 + hooks.yaml | 6 ++ pre_commit_hooks/sort_simple_yaml.py | 123 +++++++++++++++++++++++++++ setup.py | 1 + tests/sort_simple_yaml_test.py | 120 ++++++++++++++++++++++++++ 7 files changed, 258 insertions(+) create mode 100755 pre_commit_hooks/sort_simple_yaml.py create mode 100644 tests/sort_simple_yaml_test.py diff --git a/.gitignore b/.gitignore index 2626934..6fdf044 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.iml *.py[co] .*.sw[a-z] +.cache .coverage .idea .project diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index bda3f76..e7f433b 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -147,6 +147,12 @@ entry: requirements-txt-fixer language: python files: requirements.*\.txt$ +- id: sort-simple-yaml + name: Sort simple YAML files + language: python + entry: sort-simple-yaml + description: Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks. + files: '^$' - id: trailing-whitespace name: Trim Trailing Whitespace description: This hook trims trailing whitespace. diff --git a/README.md b/README.md index 3b62234..8db7eef 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Add this to your `.pre-commit-config.yaml` - `--no-sort-keys` - when autofixing, retain the original key ordering (instead of sorting the keys) - `--top-keys comma,separated,keys` - Keys to keep at the top of mappings. - `requirements-txt-fixer` - Sorts entries in requirements.txt +- `sort-simple-yaml` - Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks. - `trailing-whitespace` - Trims trailing whitespace. - Markdown linebreak trailing spaces preserved for `.md` and`.markdown`; use `args: ['--markdown-linebreak-ext=txt,text']` to add other extensions, diff --git a/hooks.yaml b/hooks.yaml index bda3f76..e7f433b 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -147,6 +147,12 @@ entry: requirements-txt-fixer language: python files: requirements.*\.txt$ +- id: sort-simple-yaml + name: Sort simple YAML files + language: python + entry: sort-simple-yaml + description: Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks. + files: '^$' - id: trailing-whitespace name: Trim Trailing Whitespace description: This hook trims trailing whitespace. diff --git a/pre_commit_hooks/sort_simple_yaml.py b/pre_commit_hooks/sort_simple_yaml.py new file mode 100755 index 0000000..7afae91 --- /dev/null +++ b/pre_commit_hooks/sort_simple_yaml.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +"""Sort a simple YAML file, keeping blocks of comments and definitions +together. + +We assume a strict subset of YAML that looks like: + + # block of header comments + # here that should always + # be at the top of the file + + # optional comments + # can go here + key: value + key: value + + key: value + +In other words, we don't sort deeper than the top layer, and might corrupt +complicated YAML files. +""" +from __future__ import print_function + +import argparse + + +QUOTES = ["'", '"'] + + +def sort(lines): + """Sort a YAML file in alphabetical order, keeping blocks together. + + :param lines: array of strings (without newlines) + :return: sorted array of strings + """ + # make a copy of lines since we will clobber it + lines = list(lines) + new_lines = parse_block(lines, header=True) + + for block in sorted(parse_blocks(lines), key=first_key): + if new_lines: + new_lines.append('') + new_lines.extend(block) + + return new_lines + + +def parse_block(lines, header=False): + """Parse and return a single block, popping off the start of `lines`. + + If parsing a header block, we stop after we reach a line that is not a + comment. Otherwise, we stop after reaching an empty line. + + :param lines: list of lines + :param header: whether we are parsing a header block + :return: list of lines that form the single block + """ + block_lines = [] + while lines and lines[0] and (not header or lines[0].startswith('#')): + block_lines.append(lines.pop(0)) + return block_lines + + +def parse_blocks(lines): + """Parse and return all possible blocks, popping off the start of `lines`. + + :param lines: list of lines + :return: list of blocks, where each block is a list of lines + """ + blocks = [] + + while lines: + if lines[0] == '': + lines.pop(0) + else: + blocks.append(parse_block(lines)) + + return blocks + + +def first_key(lines): + """Returns a string representing the sort key of a block. + + The sort key is the first YAML key we encounter, ignoring comments, and + stripping leading quotes. + + >>> print(test) + # some comment + 'foo': true + >>> first_key(test) + 'foo' + """ + for line in lines: + if line.startswith('#'): + continue + if any(line.startswith(quote) for quote in QUOTES): + return line[1:] + return line + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument('filenames', nargs='*', help='Filenames to fix') + args = parser.parse_args(argv) + + retval = 0 + + for filename in args.filenames: + with open(filename, 'r+') as f: + lines = [line.rstrip() for line in f.readlines()] + new_lines = sort(lines) + + if lines != new_lines: + print("Fixing file `{filename}`".format(filename=filename)) + f.seek(0) + f.write("\n".join(new_lines) + "\n") + f.truncate() + retval = 1 + + return retval + + +if __name__ == '__main__': + exit(main()) diff --git a/setup.py b/setup.py index 4abb7a2..af21e16 100644 --- a/setup.py +++ b/setup.py @@ -55,6 +55,7 @@ setup( 'no-commit-to-branch = pre_commit_hooks.no_commit_to_branch:main', 'pretty-format-json = pre_commit_hooks.pretty_format_json:pretty_format_json', 'requirements-txt-fixer = pre_commit_hooks.requirements_txt_fixer:fix_requirements_txt', + 'sort-simple-yaml = pre_commit_hooks.sort_simple_yaml:main', 'trailing-whitespace-fixer = pre_commit_hooks.trailing_whitespace_fixer:fix_trailing_whitespace', ], }, diff --git a/tests/sort_simple_yaml_test.py b/tests/sort_simple_yaml_test.py new file mode 100644 index 0000000..176d12f --- /dev/null +++ b/tests/sort_simple_yaml_test.py @@ -0,0 +1,120 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +import os + +import pytest + +from pre_commit_hooks.sort_simple_yaml import first_key +from pre_commit_hooks.sort_simple_yaml import main +from pre_commit_hooks.sort_simple_yaml import parse_block +from pre_commit_hooks.sort_simple_yaml import parse_blocks +from pre_commit_hooks.sort_simple_yaml import sort + +RETVAL_GOOD = 0 +RETVAL_BAD = 1 +TEST_SORTS = [ + ( + ['c: true', '', 'b: 42', 'a: 19'], + ['b: 42', 'a: 19', '', 'c: true'], + RETVAL_BAD, + ), + + ( + ['# i am', '# a header', '', 'c: true', '', 'b: 42', 'a: 19'], + ['# i am', '# a header', '', 'b: 42', 'a: 19', '', 'c: true'], + RETVAL_BAD, + ), + + ( + ['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'], + ['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'], + RETVAL_GOOD, + ), + + ( + ['# i am', '# a header'], + ['# i am', '# a header'], + RETVAL_GOOD, + ), +] + + +@pytest.mark.parametrize('bad_lines,good_lines,retval', TEST_SORTS) +def test_integration_good_bad_lines(tmpdir, bad_lines, good_lines, retval): + file_path = os.path.join(tmpdir.strpath, 'foo.yaml') + + with open(file_path, 'w') as f: + f.write("\n".join(bad_lines) + "\n") + + assert main([file_path]) == retval + + with open(file_path, 'r') as f: + assert [line.rstrip() for line in f.readlines()] == good_lines + + +def test_parse_header(): + lines = ['# some header', '# is here', '', 'this is not a header'] + assert parse_block(lines, header=True) == ['# some header', '# is here'] + assert lines == ['', 'this is not a header'] + + lines = ['this is not a header'] + assert parse_block(lines, header=True) == [] + assert lines == ['this is not a header'] + + +def test_parse_block(): + # a normal block + lines = ['a: 42', 'b: 17', '', 'c: 19'] + assert parse_block(lines) == ['a: 42', 'b: 17'] + assert lines == ['', 'c: 19'] + + # a block at the end + lines = ['c: 19'] + assert parse_block(lines) == ['c: 19'] + assert lines == [] + + # no block + lines = [] + assert parse_block(lines) == [] + assert lines == [] + + +def test_parse_blocks(): + # normal blocks + lines = ['a: 42', 'b: 17', '', 'c: 19'] + assert parse_blocks(lines) == [['a: 42', 'b: 17'], ['c: 19']] + assert lines == [] + + # a single block + lines = ['a: 42', 'b: 17'] + assert parse_blocks(lines) == [['a: 42', 'b: 17']] + assert lines == [] + + # no blocks + lines = [] + assert parse_blocks(lines) == [] + assert lines == [] + + +def test_first_key(): + # first line + lines = ['a: 42', 'b: 17', '', 'c: 19'] + assert first_key(lines) == 'a: 42' + + # second line + lines = ['# some comment', 'a: 42', 'b: 17', '', 'c: 19'] + assert first_key(lines) == 'a: 42' + + # second line with quotes + lines = ['# some comment', '"a": 42', 'b: 17', '', 'c: 19'] + assert first_key(lines) == 'a": 42' + + # no lines + lines = [] + assert first_key(lines) is None + + +@pytest.mark.parametrize('bad_lines,good_lines,_', TEST_SORTS) +def test_sort(bad_lines, good_lines, _): + assert sort(bad_lines) == good_lines From 7ccfa05f2fed8499947011ba1cb37d19cdb76774 Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Fri, 23 Jun 2017 17:19:21 -0700 Subject: [PATCH 018/114] Fix NoneTypeError when requirements file is empty --- pre_commit_hooks/requirements_txt_fixer.py | 19 +++++++++++-------- tests/requirements_txt_fixer_test.py | 2 ++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py index efa1906..41e1ffc 100644 --- a/pre_commit_hooks/requirements_txt_fixer.py +++ b/pre_commit_hooks/requirements_txt_fixer.py @@ -30,21 +30,25 @@ class Requirement(object): def fix_requirements(f): requirements = [] - before = [] + before = list(f) after = [] - for line in f: - before.append(line) + before_string = b''.join(before) - # If the most recent requirement object has a value, then it's time to - # start building the next requirement object. + # If the file is empty (i.e. only whitespace/newlines) exit early + if before_string.strip() == b'': + return 0 + + for line in before: + # If the most recent requirement object has a value, then it's + # time to start building the next requirement object. if not len(requirements) or requirements[-1].value is not None: requirements.append(Requirement()) requirement = requirements[-1] - # If we see a newline before any requirements, then this is a top of - # file comment. + # If we see a newline before any requirements, then this is a + # top of file comment. if len(requirements) == 1 and line.strip() == b'': if len(requirement.comments) and requirement.comments[0].startswith(b'#'): requirement.value = b'\n' @@ -60,7 +64,6 @@ def fix_requirements(f): after.append(comment) after.append(requirement.value) - before_string = b''.join(before) after_string = b''.join(after) if before_string == after_string: diff --git a/tests/requirements_txt_fixer_test.py b/tests/requirements_txt_fixer_test.py index 1c590a5..33f6a47 100644 --- a/tests/requirements_txt_fixer_test.py +++ b/tests/requirements_txt_fixer_test.py @@ -5,6 +5,8 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement # Input, expected return value, expected output TESTS = ( + (b'', 0, b''), + (b'\n', 0, b'\n'), (b'foo\nbar\n', 1, b'bar\nfoo\n'), (b'bar\nfoo\n', 0, b'bar\nfoo\n'), (b'#comment1\nfoo\n#comment2\nbar\n', 1, b'#comment2\nbar\n#comment1\nfoo\n'), From 89ddf178883e96a1f4452cc30df287deb0f624c4 Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Sun, 25 Jun 2017 09:48:16 -0700 Subject: [PATCH 019/114] Inline tuple parameterized test tuple --- tests/file_contents_sorter_test.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py index 7b3d098..5f4dc5b 100644 --- a/tests/file_contents_sorter_test.py +++ b/tests/file_contents_sorter_test.py @@ -5,21 +5,20 @@ from pre_commit_hooks.file_contents_sorter import main from pre_commit_hooks.file_contents_sorter import PASS -# Input, expected return value, expected output -TESTS = ( - (b'', PASS, b''), - (b'lonesome\n', PASS, b'lonesome\n'), - (b'missing_newline', PASS, b'missing_newline'), - (b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'), - (b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'), - (b'C\nc\n', PASS, b'C\nc\n'), - (b'c\nC\n', FAIL, b'C\nc\n'), - (b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'), - (b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'), +@pytest.mark.parametrize( + ('input_s', 'expected_retval', 'output'), + ( + (b'', PASS, b''), + (b'lonesome\n', PASS, b'lonesome\n'), + (b'missing_newline', PASS, b'missing_newline'), + (b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'), + (b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'), + (b'C\nc\n', PASS, b'C\nc\n'), + (b'c\nC\n', FAIL, b'C\nc\n'), + (b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'), + (b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'), + ) ) - - -@pytest.mark.parametrize(('input_s', 'expected_retval', 'output'), TESTS) def test_integration(input_s, expected_retval, output, tmpdir): path = tmpdir.join('file.txt') path.write_binary(input_s) From 844d9830de6731532b21d4d9f24ccba1e16acfae Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Sun, 25 Jun 2017 10:14:58 -0700 Subject: [PATCH 020/114] Some style tweaks --- pre_commit_hooks/requirements_txt_fixer.py | 17 ++++---- tests/requirements_txt_fixer_test.py | 46 +++++++++++++--------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py index 41e1ffc..ffabf2a 100644 --- a/pre_commit_hooks/requirements_txt_fixer.py +++ b/pre_commit_hooks/requirements_txt_fixer.py @@ -3,6 +3,10 @@ from __future__ import print_function import argparse +PASS = 0 +FAIL = 1 + + class Requirement(object): def __init__(self): @@ -30,14 +34,14 @@ class Requirement(object): def fix_requirements(f): requirements = [] - before = list(f) + before = tuple(f) after = [] before_string = b''.join(before) # If the file is empty (i.e. only whitespace/newlines) exit early if before_string.strip() == b'': - return 0 + return PASS for line in before: # If the most recent requirement object has a value, then it's @@ -60,19 +64,18 @@ def fix_requirements(f): requirement.value = line for requirement in sorted(requirements): - for comment in requirement.comments: - after.append(comment) + after.extend(requirement.comments) after.append(requirement.value) after_string = b''.join(after) if before_string == after_string: - return 0 + return PASS else: f.seek(0) f.write(after_string) f.truncate() - return 1 + return FAIL def fix_requirements_txt(argv=None): @@ -80,7 +83,7 @@ def fix_requirements_txt(argv=None): parser.add_argument('filenames', nargs='*', help='Filenames to fix') args = parser.parse_args(argv) - retv = 0 + retv = PASS for arg in args.filenames: with open(arg, 'rb+') as file_obj: diff --git a/tests/requirements_txt_fixer_test.py b/tests/requirements_txt_fixer_test.py index 33f6a47..3681cc6 100644 --- a/tests/requirements_txt_fixer_test.py +++ b/tests/requirements_txt_fixer_test.py @@ -1,33 +1,41 @@ import pytest +from pre_commit_hooks.requirements_txt_fixer import FAIL from pre_commit_hooks.requirements_txt_fixer import fix_requirements_txt +from pre_commit_hooks.requirements_txt_fixer import PASS from pre_commit_hooks.requirements_txt_fixer import Requirement -# Input, expected return value, expected output -TESTS = ( - (b'', 0, b''), - (b'\n', 0, b'\n'), - (b'foo\nbar\n', 1, b'bar\nfoo\n'), - (b'bar\nfoo\n', 0, b'bar\nfoo\n'), - (b'#comment1\nfoo\n#comment2\nbar\n', 1, b'#comment2\nbar\n#comment1\nfoo\n'), - (b'#comment1\nbar\n#comment2\nfoo\n', 0, b'#comment1\nbar\n#comment2\nfoo\n'), - (b'#comment\n\nfoo\nbar\n', 1, b'#comment\n\nbar\nfoo\n'), - (b'#comment\n\nbar\nfoo\n', 0, b'#comment\n\nbar\nfoo\n'), - (b'\nfoo\nbar\n', 1, b'bar\n\nfoo\n'), - (b'\nbar\nfoo\n', 0, b'\nbar\nfoo\n'), - (b'pyramid==1\npyramid-foo==2\n', 0, b'pyramid==1\npyramid-foo==2\n'), - (b'ocflib\nDjango\nPyMySQL\n', 1, b'Django\nocflib\nPyMySQL\n'), - (b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n', 1, b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n'), + +@pytest.mark.parametrize( + ('input_s', 'expected_retval', 'output'), + ( + (b'', PASS, b''), + (b'\n', PASS, b'\n'), + (b'foo\nbar\n', FAIL, b'bar\nfoo\n'), + (b'bar\nfoo\n', PASS, b'bar\nfoo\n'), + (b'#comment1\nfoo\n#comment2\nbar\n', FAIL, b'#comment2\nbar\n#comment1\nfoo\n'), + (b'#comment1\nbar\n#comment2\nfoo\n', PASS, b'#comment1\nbar\n#comment2\nfoo\n'), + (b'#comment\n\nfoo\nbar\n', FAIL, b'#comment\n\nbar\nfoo\n'), + (b'#comment\n\nbar\nfoo\n', PASS, b'#comment\n\nbar\nfoo\n'), + (b'\nfoo\nbar\n', FAIL, b'bar\n\nfoo\n'), + (b'\nbar\nfoo\n', PASS, b'\nbar\nfoo\n'), + (b'pyramid==1\npyramid-foo==2\n', PASS, b'pyramid==1\npyramid-foo==2\n'), + (b'ocflib\nDjango\nPyMySQL\n', FAIL, b'Django\nocflib\nPyMySQL\n'), + ( + b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n', + FAIL, + b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n' + ), + ) ) - - -@pytest.mark.parametrize(('input_s', 'expected_retval', 'output'), TESTS) def test_integration(input_s, expected_retval, output, tmpdir): path = tmpdir.join('file.txt') path.write_binary(input_s) - assert fix_requirements_txt([path.strpath]) == expected_retval + output_retval = fix_requirements_txt([path.strpath]) + assert path.read_binary() == output + assert output_retval == expected_retval def test_requirement_object(): From 4c421e2ed1da0ed6638003c1c94c3bb13894d94e Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Sun, 25 Jun 2017 10:22:10 -0700 Subject: [PATCH 021/114] Put argument parsing back into main() --- pre_commit_hooks/file_contents_sorter.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py index e01eb8c..6fa3bc0 100644 --- a/pre_commit_hooks/file_contents_sorter.py +++ b/pre_commit_hooks/file_contents_sorter.py @@ -18,7 +18,7 @@ FAIL = 1 def sort_file_contents(f): - before = list(f) + before = tuple(f) after = sorted(before) before_string = b''.join(before) @@ -33,15 +33,10 @@ def sort_file_contents(f): return FAIL -def parse_commandline_input(argv): +def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument('filenames', nargs='+', help='Files to sort') args = parser.parse_args(argv) - return args - - -def main(argv=None): - args = parse_commandline_input(argv) retv = PASS From 7cfec24f77881fcf01a65d21b3a45da5c773a7cf Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Sun, 25 Jun 2017 14:40:03 -0700 Subject: [PATCH 022/114] Fix bug with the file-contents-sorter hook when processing file that does not end in a newline --- pre_commit_hooks/file_contents_sorter.py | 19 +++++++++---------- tests/file_contents_sorter_test.py | 4 ++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py index 6fa3bc0..b66cc72 100644 --- a/pre_commit_hooks/file_contents_sorter.py +++ b/pre_commit_hooks/file_contents_sorter.py @@ -18,19 +18,18 @@ FAIL = 1 def sort_file_contents(f): - before = tuple(f) + before = [line.strip(b'\n\r') for line in f if line.strip()] after = sorted(before) - before_string = b''.join(before) - after_string = b''.join(after) - - if before_string == after_string: + if before == after: return PASS - else: - f.seek(0) - f.write(after_string) - f.truncate() - return FAIL + + after_string = b'\n'.join(after) + b'\n' + + f.seek(0) + f.write(after_string) + f.truncate() + return FAIL def main(argv=None): diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py index 5f4dc5b..b635eb0 100644 --- a/tests/file_contents_sorter_test.py +++ b/tests/file_contents_sorter_test.py @@ -11,12 +11,16 @@ from pre_commit_hooks.file_contents_sorter import PASS (b'', PASS, b''), (b'lonesome\n', PASS, b'lonesome\n'), (b'missing_newline', PASS, b'missing_newline'), + (b'newline\nmissing', FAIL, b'missing\nnewline\n'), + (b'missing\nnewline', PASS, b'missing\nnewline'), (b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'), (b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'), (b'C\nc\n', PASS, b'C\nc\n'), (b'c\nC\n', FAIL, b'C\nc\n'), (b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'), (b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'), + (b'extra\n\n\nwhitespace\n', PASS, b'extra\n\n\nwhitespace\n'), + (b'whitespace\n\n\nextra\n', FAIL, b'extra\nwhitespace\n'), ) ) def test_integration(input_s, expected_retval, output, tmpdir): From 5dd1819e8b6757490f8c20b51df219d714d4ab0e Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Sun, 25 Jun 2017 15:37:58 -0700 Subject: [PATCH 023/114] Warn users of file-contents-sorter that blank lines are removed and comments are not respected --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 92fb408..6e268ff 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Add this to your `.pre-commit-config.yaml` with single quoted strings. - `end-of-file-fixer` - Makes sure files end in a newline and only a newline. - `fix-encoding-pragma` - Add `# -*- coding: utf-8 -*-` to the top of python files. -- `file-contents-sorter` - Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input to it. +- `file-contents-sorter` - Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input to it. Note that this hook WILL remove blank lines and does NOT respect any comments. - To remove the coding pragma pass `--remove` (useful in a python3-only codebase) - `flake8` - Run flake8 on your python files. - `forbid-new-submodules` - Prevent addition of new git submodules. From 7102e0c8a3db26c25a3795b098258c02c74649af Mon Sep 17 00:00:00 2001 From: Daniel Gallagher Date: Sun, 25 Jun 2017 19:51:50 -0700 Subject: [PATCH 024/114] file-contents-sorter should add newline at end of files missing newlines Make an explicit 'else' path for readability --- pre_commit_hooks/file_contents_sorter.py | 19 ++++++++++--------- tests/file_contents_sorter_test.py | 8 ++++---- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py index b66cc72..fe7f7ee 100644 --- a/pre_commit_hooks/file_contents_sorter.py +++ b/pre_commit_hooks/file_contents_sorter.py @@ -18,18 +18,19 @@ FAIL = 1 def sort_file_contents(f): - before = [line.strip(b'\n\r') for line in f if line.strip()] - after = sorted(before) - - if before == after: - return PASS + before = list(f) + after = sorted([line.strip(b'\n\r') for line in before if line.strip()]) + before_string = b''.join(before) after_string = b'\n'.join(after) + b'\n' - f.seek(0) - f.write(after_string) - f.truncate() - return FAIL + if before_string == after_string: + return PASS + else: + f.seek(0) + f.write(after_string) + f.truncate() + return FAIL def main(argv=None): diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py index b635eb0..2c85c8a 100644 --- a/tests/file_contents_sorter_test.py +++ b/tests/file_contents_sorter_test.py @@ -8,18 +8,18 @@ from pre_commit_hooks.file_contents_sorter import PASS @pytest.mark.parametrize( ('input_s', 'expected_retval', 'output'), ( - (b'', PASS, b''), + (b'', FAIL, b'\n'), (b'lonesome\n', PASS, b'lonesome\n'), - (b'missing_newline', PASS, b'missing_newline'), + (b'missing_newline', FAIL, b'missing_newline\n'), (b'newline\nmissing', FAIL, b'missing\nnewline\n'), - (b'missing\nnewline', PASS, b'missing\nnewline'), + (b'missing\nnewline', FAIL, b'missing\nnewline\n'), (b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'), (b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'), (b'C\nc\n', PASS, b'C\nc\n'), (b'c\nC\n', FAIL, b'C\nc\n'), (b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'), (b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'), - (b'extra\n\n\nwhitespace\n', PASS, b'extra\n\n\nwhitespace\n'), + (b'extra\n\n\nwhitespace\n', FAIL, b'extra\nwhitespace\n'), (b'whitespace\n\n\nextra\n', FAIL, b'extra\nwhitespace\n'), ) ) From c4720434ba44e12f36c411b09586657f5a8c3ab7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 2 Jul 2017 19:55:58 -0700 Subject: [PATCH 025/114] Make check-symlinks 0.15.0 compatible --- .pre-commit-hooks.yaml | 1 + hooks.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 0604585..b9bcaac 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -63,6 +63,7 @@ language: python # Match all files files: '' + types: [symlink] - id: check-xml name: Check Xml description: This hook checks xml files for parseable syntax. diff --git a/hooks.yaml b/hooks.yaml index 0604585..b9bcaac 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -63,6 +63,7 @@ language: python # Match all files files: '' + types: [symlink] - id: check-xml name: Check Xml description: This hook checks xml files for parseable syntax. From abea6d293d9c43736c6923e680d8466164b45fab Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 2 Jul 2017 20:57:10 -0700 Subject: [PATCH 026/114] Use 0.15.0 types --- .pre-commit-config.yaml | 2 +- .pre-commit-hooks.yaml | 102 +++++++++++---- hooks.yaml | 272 ++++++++++++++++++++-------------------- tests/meta_test.py | 44 ++++++- 4 files changed, 255 insertions(+), 165 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index be99f71..2367f89 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ - id: requirements-txt-fixer - id: flake8 - repo: https://github.com/pre-commit/pre-commit - sha: v0.12.2 + sha: v0.15.0 hooks: - id: validate_config - id: validate_manifest diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 6cda2dd..dac48f0 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -3,109 +3,153 @@ description: "Runs autopep8 over python source. If you configure additional arguments you'll want to at least include -i." entry: autopep8-wrapper language: python - files: \.py$ + types: [python] args: [-i] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-added-large-files name: Check for added large files description: Prevent giant files from being committed entry: check-added-large-files language: python - # Match all files + # for backward compatibility files: '' + minimum_pre_commit_version: 0.15.0 - id: check-ast name: Check python ast description: Simply check whether the files parse as valid python. entry: check-ast language: python - files: '\.py$' + types: [python] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-byte-order-marker name: Check for byte-order marker description: Forbid files which have a UTF-8 byte-order marker entry: check-byte-order-marker language: python - files: '\.py$' + types: [python] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-case-conflict name: Check for case conflicts description: Check for files that would conflict in case-insensitive filesystems entry: check-case-conflict language: python - # Match all files + # for backward compatibility files: '' + minimum_pre_commit_version: 0.15.0 - id: check-docstring-first name: Check docstring is first description: Checks a common error of defining a docstring after code. entry: check-docstring-first language: python - files: \.py$ + types: [python] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-json name: Check JSON description: This hook checks json files for parseable syntax. entry: check-json language: python - files: \.json$ + types: [json] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: pretty-format-json name: Pretty format JSON description: This hook sets a standard for formatting JSON files. entry: pretty-format-json language: python - files: \.json$ + types: [json] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-merge-conflict name: Check for merge conflicts description: Check for files that contain merge conflict strings. entry: check-merge-conflict language: python - # Match all files + types: [text] + # for backward compatibility files: '' + minimum_pre_commit_version: 0.15.0 - id: check-symlinks name: Check for broken symlinks description: Checks for symlinks which do not point to anything. entry: check-symlinks language: python - # Match all files - files: '' types: [symlink] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-xml name: Check Xml description: This hook checks xml files for parseable syntax. entry: check-xml language: python - files: \.xml$ + types: [xml] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-yaml name: Check Yaml description: This hook checks yaml files for parseable syntax. entry: check-yaml language: python - files: \.(yaml|yml|eyaml)$ + types: [yaml] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: debug-statements name: Debug Statements (Python) description: This hook checks that debug statements (pdb, ipdb, pudb) are not imported on commit. entry: debug-statement-hook language: python - files: \.py$ + types: [python] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: detect-aws-credentials name: Detect AWS Credentials description: Detects *your* aws credentials from the aws cli credentials file entry: detect-aws-credentials language: python + types: [text] + # for backward compatibility files: '' + minimum_pre_commit_version: 0.15.0 - id: detect-private-key name: Detect Private Key description: Detects the presence of private keys entry: detect-private-key language: python + types: [text] + # for backward compatibility files: '' + minimum_pre_commit_version: 0.15.0 - id: double-quote-string-fixer name: Fix double quoted strings description: This hook replaces double quoted strings with single quoted strings entry: double-quote-string-fixer language: python - files: \.py$ + types: [python] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: end-of-file-fixer name: Fix End of Files description: Ensures that a file is either empty, or ends with one newline. entry: end-of-file-fixer language: python - files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$ + types: [text] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: file-contents-sorter name: File Contents Sorter description: Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input in your .pre-commit-config.yaml file. @@ -117,19 +161,27 @@ language: python entry: fix-encoding-pragma description: 'Add # -*- coding: utf-8 -*- to the top of python files' - files: \.py$ + types: [python] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: flake8 name: Flake8 description: This hook runs flake8. entry: flake8 language: python - files: \.py$ + types: [python] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: forbid-new-submodules name: Forbid new submodules language: python entry: forbid-new-submodules description: Prevent addition of new git submodules + # for backward compatibility files: '' + minimum_pre_commit_version: 0.15.0 - id: name-tests-test name: Tests should end in _test.py description: This verifies that test files are named correctly @@ -140,14 +192,19 @@ name: "Don't commit to branch" entry: no-commit-to-branch language: python - files: .* always_run: true + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: pyflakes name: Pyflakes (DEPRECATED, use flake8) description: This hook runs pyflakes. (This is deprecated, use flake8). entry: pyflakes language: python - files: \.py$ + types: [python] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: requirements-txt-fixer name: Fix requirements.txt description: Sorts entries in requirements.txt @@ -165,4 +222,7 @@ description: This hook trims trailing whitespace. entry: trailing-whitespace-fixer language: python - files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$ + types: [text] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 diff --git a/hooks.yaml b/hooks.yaml index 6cda2dd..7a4f933 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -1,168 +1,162 @@ - id: autopep8-wrapper - name: autopep8 wrapper - description: "Runs autopep8 over python source. If you configure additional arguments you'll want to at least include -i." - entry: autopep8-wrapper - language: python - files: \.py$ - args: [-i] + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-added-large-files - name: Check for added large files - description: Prevent giant files from being committed - entry: check-added-large-files - language: python - # Match all files + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version files: '' + minimum_pre_commit_version: 0.15.0 - id: check-ast - name: Check python ast - description: Simply check whether the files parse as valid python. - entry: check-ast - language: python - files: '\.py$' + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-byte-order-marker - name: Check for byte-order marker - description: Forbid files which have a UTF-8 byte-order marker - entry: check-byte-order-marker - language: python - files: '\.py$' + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-case-conflict - name: Check for case conflicts - description: Check for files that would conflict in case-insensitive filesystems - entry: check-case-conflict - language: python - # Match all files + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version files: '' + minimum_pre_commit_version: 0.15.0 - id: check-docstring-first - name: Check docstring is first - description: Checks a common error of defining a docstring after code. - entry: check-docstring-first - language: python - files: \.py$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-json - name: Check JSON - description: This hook checks json files for parseable syntax. - entry: check-json - language: python - files: \.json$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: pretty-format-json - name: Pretty format JSON - description: This hook sets a standard for formatting JSON files. - entry: pretty-format-json - language: python - files: \.json$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-merge-conflict - name: Check for merge conflicts - description: Check for files that contain merge conflict strings. - entry: check-merge-conflict - language: python - # Match all files + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version files: '' + minimum_pre_commit_version: 0.15.0 - id: check-symlinks - name: Check for broken symlinks - description: Checks for symlinks which do not point to anything. - entry: check-symlinks - language: python - # Match all files + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version files: '' - types: [symlink] + minimum_pre_commit_version: 0.15.0 - id: check-xml - name: Check Xml - description: This hook checks xml files for parseable syntax. - entry: check-xml - language: python - files: \.xml$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-yaml - name: Check Yaml - description: This hook checks yaml files for parseable syntax. - entry: check-yaml - language: python - files: \.(yaml|yml|eyaml)$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: debug-statements - name: Debug Statements (Python) - description: This hook checks that debug statements (pdb, ipdb, pudb) are not imported on commit. - entry: debug-statement-hook - language: python - files: \.py$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: detect-aws-credentials - name: Detect AWS Credentials - description: Detects *your* aws credentials from the aws cli credentials file - entry: detect-aws-credentials - language: python + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version files: '' + minimum_pre_commit_version: 0.15.0 - id: detect-private-key - name: Detect Private Key - description: Detects the presence of private keys - entry: detect-private-key - language: python + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version files: '' + minimum_pre_commit_version: 0.15.0 - id: double-quote-string-fixer - name: Fix double quoted strings - description: This hook replaces double quoted strings with single quoted strings - entry: double-quote-string-fixer - language: python - files: \.py$ -- id: end-of-file-fixer - name: Fix End of Files - description: Ensures that a file is either empty, or ends with one newline. - entry: end-of-file-fixer - language: python - files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$ -- id: file-contents-sorter - name: File Contents Sorter - description: Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input in your .pre-commit-config.yaml file. - entry: file-contents-sorter - language: python - files: '^$' -- id: fix-encoding-pragma - name: Fix python encoding pragma - language: python - entry: fix-encoding-pragma - description: 'Add # -*- coding: utf-8 -*- to the top of python files' - files: \.py$ -- id: flake8 - name: Flake8 - description: This hook runs flake8. - entry: flake8 - language: python - files: \.py$ -- id: forbid-new-submodules - name: Forbid new submodules - language: python - entry: forbid-new-submodules - description: Prevent addition of new git submodules + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version files: '' + minimum_pre_commit_version: 0.15.0 +- id: end-of-file-fixer + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 +- id: file-contents-sorter + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 +- id: fix-encoding-pragma + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 +- id: flake8 + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 +- id: forbid-new-submodules + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: name-tests-test - name: Tests should end in _test.py - description: This verifies that test files are named correctly - entry: name-tests-test - language: python - files: tests/.+\.py$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: no-commit-to-branch - name: "Don't commit to branch" - entry: no-commit-to-branch - language: python - files: .* - always_run: true + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: pyflakes - name: Pyflakes (DEPRECATED, use flake8) - description: This hook runs pyflakes. (This is deprecated, use flake8). - entry: pyflakes - language: python - files: \.py$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: requirements-txt-fixer - name: Fix requirements.txt - description: Sorts entries in requirements.txt - entry: requirements-txt-fixer - language: python - files: requirements.*\.txt$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: sort-simple-yaml - name: Sort simple YAML files - language: python - entry: sort-simple-yaml - description: Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks. - files: '^$' + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: trailing-whitespace - name: Trim Trailing Whitespace - description: This hook trims trailing whitespace. - entry: trailing-whitespace-fixer - language: python - files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$ + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 diff --git a/tests/meta_test.py b/tests/meta_test.py index 202b3d7..29e06a8 100644 --- a/tests/meta_test.py +++ b/tests/meta_test.py @@ -1,7 +1,43 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + import io +import yaml -def test_hooks_yaml_same_contents(): - legacy_contents = io.open('hooks.yaml').read() - contents = io.open('.pre-commit-hooks.yaml').read() - assert legacy_contents == contents + +def _assert_parseable_in_old_pre_commit(hooks): + for hook in hooks: + assert {'id', 'name', 'entry', 'files', 'language'} <= set(hook) + + +def test_legacy_hooks(): + with io.open('hooks.yaml') as legacy_file: + legacy = yaml.load(legacy_file.read()) + with io.open('.pre-commit-hooks.yaml') as hooks_file: + hooks = yaml.load(hooks_file.read()) + + # The same set of hooks should be defined in both files + new_hook_ids = {hook['id'] for hook in hooks} + legacy_hook_ids = {hook['id'] for hook in legacy} + assert new_hook_ids == legacy_hook_ids + + # Both files should be parseable by pre-commit<0.15.0 + _assert_parseable_in_old_pre_commit(legacy) + _assert_parseable_in_old_pre_commit(hooks) + + # The legacy file should force upgrading + for hook in legacy: + del hook['id'] + assert hook == { + 'language': 'system', + 'name': 'upgrade-your-pre-commit-version', + 'entry': 'upgrade-your-pre-commit-version', + 'files': '', + 'minimum_pre_commit_version': '0.15.0', + } + + # Each hook should require a new version if it uses types + for hook in hooks: + if 'types' in hook: + assert hook['minimum_pre_commit_version'] == '0.15.0' From 0f1be0c52166c5eca66eecc611e661e6b09b88bc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 21 Mar 2017 10:26:34 -0700 Subject: [PATCH 027/114] Align tox.ini with .travis.yml --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index bf2eba2..9dd4c2f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] project = pre_commit_hooks # These should match the travis env list -envlist = py27,py34,py35,pypy +envlist = py27,py35,py36,pypy [testenv] deps = -rrequirements-dev.txt From 13991f09d2da51141ab28d3d3f1cbf1e841b5aab Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Sun, 2 Jul 2017 21:00:28 -0700 Subject: [PATCH 028/114] Add a checker for executables without shebangs --- .pre-commit-hooks.yaml | 9 +++++ README.md | 2 + hooks.yaml | 6 +++ .../check_executables_have_shebangs.py | 40 +++++++++++++++++++ setup.py | 1 + tests/check_executables_have_shebangs_test.py | 35 ++++++++++++++++ 6 files changed, 93 insertions(+) create mode 100644 pre_commit_hooks/check_executables_have_shebangs.py create mode 100644 tests/check_executables_have_shebangs_test.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index dac48f0..1aba741 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -51,6 +51,15 @@ # for backward compatibility files: '' minimum_pre_commit_version: 0.15.0 +- id: check-executables-have-shebangs + name: Check that executables have shebangs + description: Ensures that (non-binary) executables have a shebang. + entry: check-executables-have-shebangs + language: python + types: [text, executable] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-json name: Check JSON description: This hook checks json files for parseable syntax. diff --git a/README.md b/README.md index 6e268ff..dea0f5e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ Add this to your `.pre-commit-config.yaml` case-insensitive filesystem like MacOS HFS+ or Windows FAT. - `check-docstring-first` - Checks for a common error of placing code before the docstring. +- `check-executables-have-shebangs` - Checks that non-binary executables have a + proper shebang. - `check-json` - Attempts to load all json files to verify syntax. - `check-merge-conflict` - Check for files that contain merge conflict strings. - `check-symlinks` - Checks for symlinks which do not point to anything. diff --git a/hooks.yaml b/hooks.yaml index 7a4f933..5278bf5 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -34,6 +34,12 @@ entry: upgrade-your-pre-commit-version files: '' minimum_pre_commit_version: 0.15.0 +- id: check-executables-have-shebangs + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-json language: system name: upgrade-your-pre-commit-version diff --git a/pre_commit_hooks/check_executables_have_shebangs.py b/pre_commit_hooks/check_executables_have_shebangs.py new file mode 100644 index 0000000..89ac6e5 --- /dev/null +++ b/pre_commit_hooks/check_executables_have_shebangs.py @@ -0,0 +1,40 @@ +"""Check that executable text files have a shebang.""" +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import argparse +import pipes +import sys + + +def check_has_shebang(path): + with open(path, 'rb') as f: + first_bytes = f.read(2) + + if first_bytes != b'#!': + print( + '{path}: marked executable but has no (or invalid) shebang!\n' + " If it isn't supposed to be executable, try: chmod -x {quoted}\n" + ' If it is supposed to be executable, double-check its shebang.'.format( + path=path, + quoted=pipes.quote(path), + ), + file=sys.stderr, + ) + return 1 + else: + return 0 + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('filenames', nargs='*') + args = parser.parse_args(argv) + + retv = 0 + + for filename in args.filenames: + retv |= check_has_shebang(filename) + + return retv diff --git a/setup.py b/setup.py index c5cceb7..2d99e5d 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ setup( 'check-byte-order-marker = pre_commit_hooks.check_byte_order_marker:main', 'check-case-conflict = pre_commit_hooks.check_case_conflict:main', 'check-docstring-first = pre_commit_hooks.check_docstring_first:main', + 'check-executables-have-shebangs = pre_commit_hooks.check_executables_have_shebangs:main', 'check-json = pre_commit_hooks.check_json:check_json', 'check-merge-conflict = pre_commit_hooks.check_merge_conflict:detect_merge_conflict', 'check-symlinks = pre_commit_hooks.check_symlinks:check_symlinks', diff --git a/tests/check_executables_have_shebangs_test.py b/tests/check_executables_have_shebangs_test.py new file mode 100644 index 0000000..0e28986 --- /dev/null +++ b/tests/check_executables_have_shebangs_test.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import unicode_literals + +import pytest + +from pre_commit_hooks.check_executables_have_shebangs import main + + +@pytest.mark.parametrize('content', ( + b'#!/bin/bash\nhello world\n', + b'#!/usr/bin/env python3.6', + b'#!python', + '#!☃'.encode('UTF-8'), +)) +def test_has_shebang(content, tmpdir): + path = tmpdir.join('path') + path.write(content, 'wb') + assert main((path.strpath,)) == 0 + + +@pytest.mark.parametrize('content', ( + b'', + b' #!python\n', + b'\n#!python\n', + b'python\n', + '☃'.encode('UTF-8'), + +)) +def test_bad_shebang(content, tmpdir, capsys): + path = tmpdir.join('path') + path.write(content, 'wb') + assert main((path.strpath,)) == 1 + _, stderr = capsys.readouterr() + assert stderr.startswith('{}: marked executable but'.format(path.strpath)) From 5dbbd4dd08a9513375b7ce12207e77c728dc9822 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 2 Jul 2017 21:21:19 -0700 Subject: [PATCH 029/114] v0.9.0 --- .pre-commit-config.yaml | 2 +- CHANGELOG | 10 ++++++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2367f89..aa2c164 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v0.8.0 + sha: v0.9.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG b/CHANGELOG index f547531..9ea6c28 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +0.9.0 +===== +- Add `sort-simple-yaml` hook +- Fix `requirements-txt-fixer` for empty files +- Add `file-contents-sorter` hook for sorting flat files +- `check-merge-conflict` now recognizes rebase conflicts +- Metadata now uses `types` (and therefore requires pre-commit 0.15.0). This + allows the text processing hooks to match *all* text files (and to match + files which would only be classifiable by their shebangs). + 0.8.0 ===== - Add flag allowing missing keys to `detect-aws-credentials` diff --git a/README.md b/README.md index 6e268ff..4bb73b3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v0.8.0 # Use the ref you want to point at + sha: v0.9.0 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index c5cceb7..f5262b3 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='0.8.0', + version='0.9.0', author='Anthony Sottile', author_email='asottile@umich.edu', From 1d7aaaaa1efd27a2098eeccfd8b9e1bbb1948424 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 2 Jul 2017 21:22:35 -0700 Subject: [PATCH 030/114] Fix end of files --- testing/resources/aws_config_with_multiple_sections.ini | 2 +- testing/resources/aws_config_with_secret_and_session_token.ini | 2 +- testing/resources/aws_config_with_session_token.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/resources/aws_config_with_multiple_sections.ini b/testing/resources/aws_config_with_multiple_sections.ini index ca6a8a3..2053b9a 100644 --- a/testing/resources/aws_config_with_multiple_sections.ini +++ b/testing/resources/aws_config_with_multiple_sections.ini @@ -9,4 +9,4 @@ aws_secret_access_key = z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb aws_access_key_id = AKIAJIMMINYCRICKET0A aws_secret_access_key = ixswosj8gz3wuik405jl9k3vdajsnxfhnpui38ez [test] -aws_session_token = foo \ No newline at end of file +aws_session_token = foo diff --git a/testing/resources/aws_config_with_secret_and_session_token.ini b/testing/resources/aws_config_with_secret_and_session_token.ini index 4bd675d..4496765 100644 --- a/testing/resources/aws_config_with_secret_and_session_token.ini +++ b/testing/resources/aws_config_with_secret_and_session_token.ini @@ -2,4 +2,4 @@ [production] aws_access_key_id = AKIAVOGONSVOGONS0042 aws_secret_access_key = z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb -aws_session_token = foo \ No newline at end of file +aws_session_token = foo diff --git a/testing/resources/aws_config_with_session_token.ini b/testing/resources/aws_config_with_session_token.ini index e07f2ac..b03f127 100644 --- a/testing/resources/aws_config_with_session_token.ini +++ b/testing/resources/aws_config_with_session_token.ini @@ -1,3 +1,3 @@ # file with an AWS session token [production] -aws_session_token = foo \ No newline at end of file +aws_session_token = foo From 7192665e31cea6ace58a71e086c7248d7e5610c2 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 2 Jul 2017 22:27:09 -0700 Subject: [PATCH 031/114] v0.9.1 --- .pre-commit-config.yaml | 2 +- CHANGELOG | 4 ++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aa2c164..f39150b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v0.9.0 + sha: v0.9.1 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG b/CHANGELOG index 9ea6c28..688ef2b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +0.9.1 +===== +- Add `check-executables-have-shebangs` hook. + 0.9.0 ===== - Add `sort-simple-yaml` hook diff --git a/README.md b/README.md index a25a756..efae31c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v0.9.0 # Use the ref you want to point at + sha: v0.9.1 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index bc9c28b..701e96a 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='0.9.0', + version='0.9.1', author='Anthony Sottile', author_email='asottile@umich.edu', From e1ec204be04cd49e369512ed7bfcc6dcfd76fef5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 5 Jul 2017 12:24:03 -0700 Subject: [PATCH 032/114] Don't gpg sign during test --- tests/check_added_large_files_test.py | 7 +------ tests/forbid_new_submodules_test.py | 10 +++++++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/check_added_large_files_test.py b/tests/check_added_large_files_test.py index ce15f5c..4a84463 100644 --- a/tests/check_added_large_files_test.py +++ b/tests/check_added_large_files_test.py @@ -81,12 +81,7 @@ def test_allows_gitlfs(temp_git_dir): # pragma: no cover with temp_git_dir.as_cwd(): # Work around https://github.com/github/git-lfs/issues/913 cmd_output( - 'git', - 'commit', - '--no-gpg-sign', - '--allow-empty', - '-m', - 'foo', + 'git', 'commit', '--no-gpg-sign', '--allow-empty', '-m', 'foo', ) cmd_output('git', 'lfs', 'install') temp_git_dir.join('f.py').write('a' * 10000) diff --git a/tests/forbid_new_submodules_test.py b/tests/forbid_new_submodules_test.py index 1750e00..fb37862 100644 --- a/tests/forbid_new_submodules_test.py +++ b/tests/forbid_new_submodules_test.py @@ -10,10 +10,14 @@ from pre_commit_hooks.forbid_new_submodules import main def git_dir_with_git_dir(tmpdir): with tmpdir.as_cwd(): cmd_output('git', 'init', '.') - cmd_output('git', 'commit', '-m', 'init', '--allow-empty') + cmd_output( + 'git', 'commit', '-m', 'init', '--allow-empty', '--no-gpg-sign', + ) cmd_output('git', 'init', 'foo') - with tmpdir.join('foo').as_cwd(): - cmd_output('git', 'commit', '-m', 'init', '--allow-empty') + cmd_output( + 'git', 'commit', '-m', 'init', '--allow-empty', '--no-gpg-sign', + cwd=tmpdir.join('foo').strpath, + ) yield From 8ad9e7c94c2f57ad17f0054e60864c31a2f194d7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 5 Jul 2017 12:38:21 -0700 Subject: [PATCH 033/114] Report full version in check-ast --- .coveragerc | 2 ++ pre_commit_hooks/check_ast.py | 10 +++++----- tox.ini | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.coveragerc b/.coveragerc index 714aaa1..732613b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,6 +10,8 @@ omit = get-git-lfs.py [report] +show_missing = True +skip_covered = True exclude_lines = # Have to re-enable the standard pragma \#\s*pragma: no cover diff --git a/pre_commit_hooks/check_ast.py b/pre_commit_hooks/check_ast.py index 169e077..9809a3f 100644 --- a/pre_commit_hooks/check_ast.py +++ b/pre_commit_hooks/check_ast.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import argparse import ast -import os.path +import platform import sys import traceback @@ -14,16 +14,16 @@ def check_ast(argv=None): parser.add_argument('filenames', nargs='*') args = parser.parse_args(argv) - _, interpreter = os.path.split(sys.executable) - retval = 0 for filename in args.filenames: try: ast.parse(open(filename, 'rb').read(), filename=filename) except SyntaxError: - print('{}: failed parsing with {}:'.format( - filename, interpreter, + print('{}: failed parsing with {} {}:'.format( + filename, + platform.python_implementation(), + sys.version.partition(' ')[0], )) print('\n{}'.format( ' ' + traceback.format_exc().replace('\n', '\n ') diff --git a/tox.ini b/tox.ini index 9dd4c2f..d0863eb 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ setenv = commands = coverage erase coverage run -m pytest {posargs:tests} - coverage report --show-missing --fail-under 100 + coverage report --fail-under 100 pre-commit install -f --install-hooks pre-commit run --all-files From 2a902e0a073647334076360bad571477aea1f1cf Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 12 Jul 2017 18:35:24 -0700 Subject: [PATCH 034/114] Use asottile/add-trailing-comma --- .pre-commit-config.yaml | 6 +++++- pre_commit_hooks/check_added_large_files.py | 2 +- pre_commit_hooks/check_ast.py | 2 +- pre_commit_hooks/check_case_conflict.py | 2 +- pre_commit_hooks/check_docstring_first.py | 5 +++-- pre_commit_hooks/check_merge_conflict.py | 2 +- pre_commit_hooks/debug_statement_hook.py | 4 ++-- pre_commit_hooks/detect_aws_credentials.py | 12 ++++++------ pre_commit_hooks/pretty_format_json.py | 2 +- pre_commit_hooks/tests_should_end_in_test.py | 6 +++--- pre_commit_hooks/trailing_whitespace_fixer.py | 6 +++--- tests/check_docstring_first_test.py | 4 ++-- tests/check_merge_conflict_test.py | 6 +++--- tests/debug_statement_hook_test.py | 4 ++-- tests/detect_aws_credentials_test.py | 16 ++++++++-------- tests/file_contents_sorter_test.py | 2 +- tests/fix_encoding_pragma_test.py | 4 ++-- tests/requirements_txt_fixer_test.py | 4 ++-- tests/trailing_whitespace_fixer_test.py | 2 +- 19 files changed, 48 insertions(+), 43 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f39150b..9467acf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,6 +23,10 @@ - id: reorder-python-imports language_version: python2.7 - repo: https://github.com/asottile/pyupgrade - sha: v1.0.0 + sha: v1.1.2 hooks: - id: pyupgrade +- repo: https://github.com/asottile/add-trailing-comma + sha: v0.3.0 + hooks: + - id: add-trailing-comma diff --git a/pre_commit_hooks/check_added_large_files.py b/pre_commit_hooks/check_added_large_files.py index 8d5f4c4..193b910 100644 --- a/pre_commit_hooks/check_added_large_files.py +++ b/pre_commit_hooks/check_added_large_files.py @@ -51,7 +51,7 @@ def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument( 'filenames', nargs='*', - help='Filenames pre-commit believes are changed.' + help='Filenames pre-commit believes are changed.', ) parser.add_argument( '--maxkb', type=int, default=500, diff --git a/pre_commit_hooks/check_ast.py b/pre_commit_hooks/check_ast.py index 9809a3f..1090300 100644 --- a/pre_commit_hooks/check_ast.py +++ b/pre_commit_hooks/check_ast.py @@ -26,7 +26,7 @@ def check_ast(argv=None): sys.version.partition(' ')[0], )) print('\n{}'.format( - ' ' + traceback.format_exc().replace('\n', '\n ') + ' ' + traceback.format_exc().replace('\n', '\n '), )) retval = 1 return retval diff --git a/pre_commit_hooks/check_case_conflict.py b/pre_commit_hooks/check_case_conflict.py index 3d6cf74..0f78296 100644 --- a/pre_commit_hooks/check_case_conflict.py +++ b/pre_commit_hooks/check_case_conflict.py @@ -45,7 +45,7 @@ def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument( 'filenames', nargs='*', - help='Filenames pre-commit believes are changed.' + help='Filenames pre-commit believes are changed.', ) args = parser.parse_args(argv) diff --git a/pre_commit_hooks/check_docstring_first.py b/pre_commit_hooks/check_docstring_first.py index 8e658a1..f2f5b72 100644 --- a/pre_commit_hooks/check_docstring_first.py +++ b/pre_commit_hooks/check_docstring_first.py @@ -1,4 +1,5 @@ from __future__ import absolute_import +from __future__ import print_function from __future__ import unicode_literals import argparse @@ -30,7 +31,7 @@ def check_docstring_first(src, filename=''): '{}:{} Multiple module docstrings ' '(first docstring on line {}).'.format( filename, sline, found_docstring_line, - ) + ), ) return 1 elif found_code_line is not None: @@ -38,7 +39,7 @@ def check_docstring_first(src, filename=''): '{}:{} Module docstring appears after code ' '(code seen on line {}).'.format( filename, sline, found_code_line, - ) + ), ) return 1 else: diff --git a/pre_commit_hooks/check_merge_conflict.py b/pre_commit_hooks/check_merge_conflict.py index 7d87efc..5035b6d 100644 --- a/pre_commit_hooks/check_merge_conflict.py +++ b/pre_commit_hooks/check_merge_conflict.py @@ -7,7 +7,7 @@ CONFLICT_PATTERNS = [ b'<<<<<<< ', b'======= ', b'=======\n', - b'>>>>>>> ' + b'>>>>>>> ', ] WARNING_MSG = 'Merge conflict string "{0}" found in {1}:{2}' diff --git a/pre_commit_hooks/debug_statement_hook.py b/pre_commit_hooks/debug_statement_hook.py index 902198f..d76e6e6 100644 --- a/pre_commit_hooks/debug_statement_hook.py +++ b/pre_commit_hooks/debug_statement_hook.py @@ -29,7 +29,7 @@ class ImportStatementParser(ast.NodeVisitor): def visit_ImportFrom(self, node): if node.module in DEBUG_STATEMENTS: self.debug_import_statements.append( - DebugStatement(node.module, node.lineno, node.col_offset) + DebugStatement(node.module, node.lineno, node.col_offset), ) @@ -52,7 +52,7 @@ def check_file_for_debug_statements(filename): debug_statement.line, debug_statement.col, debug_statement.name, - ) + ), ) return 1 else: diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py index 42758f0..b2afd71 100644 --- a/pre_commit_hooks/detect_aws_credentials.py +++ b/pre_commit_hooks/detect_aws_credentials.py @@ -12,7 +12,7 @@ def get_aws_credential_files_from_env(): files = set() for env_var in ( 'AWS_CONFIG_FILE', 'AWS_CREDENTIAL_FILE', 'AWS_SHARED_CREDENTIALS_FILE', - 'BOTO_CONFIG' + 'BOTO_CONFIG', ): if env_var in os.environ: files.add(os.environ[env_var]) @@ -23,7 +23,7 @@ def get_aws_secrets_from_env(): """Extract AWS secrets from environment variables.""" keys = set() for env_var in ( - 'AWS_SECRET_ACCESS_KEY', 'AWS_SECURITY_TOKEN', 'AWS_SESSION_TOKEN' + 'AWS_SECRET_ACCESS_KEY', 'AWS_SECURITY_TOKEN', 'AWS_SESSION_TOKEN', ): if env_var in os.environ: keys.add(os.environ[env_var]) @@ -50,7 +50,7 @@ def get_aws_secrets_from_file(credentials_file): for section in parser.sections(): for var in ( 'aws_secret_access_key', 'aws_security_token', - 'aws_session_token' + 'aws_session_token', ): try: keys.add(parser.get(section, var)) @@ -93,13 +93,13 @@ def main(argv=None): help=( 'Location of additional AWS credential files from which to get ' 'secret keys from' - ) + ), ) parser.add_argument( '--allow-missing-credentials', dest='allow_missing_credentials', action='store_true', - help='Allow hook to pass when no credentials are detected.' + help='Allow hook to pass when no credentials are detected.', ) args = parser.parse_args(argv) @@ -124,7 +124,7 @@ def main(argv=None): print( 'No AWS keys were found in the configured credential files and ' 'environment variables.\nPlease ensure you have the correct ' - 'setting for --credentials-file' + 'setting for --credentials-file', ) return 2 diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py index 5e04230..bb7a3d0 100644 --- a/pre_commit_hooks/pretty_format_json.py +++ b/pre_commit_hooks/pretty_format_json.py @@ -120,7 +120,7 @@ def pretty_format_json(argv=None): except simplejson.JSONDecodeError: print( "Input File {} is not a valid JSON, consider using check-json" - .format(json_file) + .format(json_file), ) return 1 diff --git a/pre_commit_hooks/tests_should_end_in_test.py b/pre_commit_hooks/tests_should_end_in_test.py index 5f9bbac..9bea20d 100644 --- a/pre_commit_hooks/tests_should_end_in_test.py +++ b/pre_commit_hooks/tests_should_end_in_test.py @@ -11,7 +11,7 @@ def validate_files(argv=None): parser.add_argument('filenames', nargs='*') parser.add_argument( '--django', default=False, action='store_true', - help='Use Django-style test naming pattern (test*.py)' + help='Use Django-style test naming pattern (test*.py)', ) args = parser.parse_args(argv) @@ -27,8 +27,8 @@ def validate_files(argv=None): retcode = 1 print( '{} does not match pattern "{}"'.format( - filename, test_name_pattern - ) + filename, test_name_pattern, + ), ) return retcode diff --git a/pre_commit_hooks/trailing_whitespace_fixer.py b/pre_commit_hooks/trailing_whitespace_fixer.py index 1ae15a9..d44750d 100644 --- a/pre_commit_hooks/trailing_whitespace_fixer.py +++ b/pre_commit_hooks/trailing_whitespace_fixer.py @@ -36,7 +36,7 @@ def fix_trailing_whitespace(argv=None): const=[], default=argparse.SUPPRESS, dest='markdown_linebreak_ext', - help='Do not preserve linebreak spaces in Markdown' + help='Do not preserve linebreak spaces in Markdown', ) parser.add_argument( '--markdown-linebreak-ext', @@ -45,7 +45,7 @@ def fix_trailing_whitespace(argv=None): default=['md,markdown'], metavar='*|EXT[,EXT,...]', nargs='?', - help='Markdown extensions (or *) for linebreak spaces' + help='Markdown extensions (or *) for linebreak spaces', ) parser.add_argument('filenames', nargs='*', help='Filenames to fix') args = parser.parse_args(argv) @@ -69,7 +69,7 @@ def fix_trailing_whitespace(argv=None): parser.error( "bad --markdown-linebreak-ext extension '{}' (has . / \\ :)\n" " (probably filename; use '--markdown-linebreak-ext=EXT')" - .format(ext) + .format(ext), ) return_code = 0 diff --git a/tests/check_docstring_first_test.py b/tests/check_docstring_first_test.py index f14880b..aa9898d 100644 --- a/tests/check_docstring_first_test.py +++ b/tests/check_docstring_first_test.py @@ -19,7 +19,7 @@ TESTS = ( '"foo"\n', 1, '{filename}:2 Module docstring appears after code ' - '(code seen on line 1).\n' + '(code seen on line 1).\n', ), # Test double docstring ( @@ -28,7 +28,7 @@ TESTS = ( '"fake docstring"\n', 1, '{filename}:3 Multiple module docstrings ' - '(first docstring on line 1).\n' + '(first docstring on line 1).\n', ), # Test multiple lines of code above ( diff --git a/tests/check_merge_conflict_test.py b/tests/check_merge_conflict_test.py index 5a2e82a..a999aca 100644 --- a/tests/check_merge_conflict_test.py +++ b/tests/check_merge_conflict_test.py @@ -45,7 +45,7 @@ def f1_is_a_conflict_file(tmpdir): 'child\n' '=======\n' 'parent\n' - '>>>>>>>' + '>>>>>>>', ) or f1.startswith( '<<<<<<< HEAD\n' 'child\n' @@ -53,7 +53,7 @@ def f1_is_a_conflict_file(tmpdir): '||||||| merged common ancestors\n' '=======\n' 'parent\n' - '>>>>>>>' + '>>>>>>>', ) or f1.startswith( # .gitconfig with [pull] rebase = preserve causes a rebase which # flips parent / child @@ -61,7 +61,7 @@ def f1_is_a_conflict_file(tmpdir): 'parent\n' '=======\n' 'child\n' - '>>>>>>>' + '>>>>>>>', ) assert os.path.exists(os.path.join('.git', 'MERGE_MSG')) yield diff --git a/tests/debug_statement_hook_test.py b/tests/debug_statement_hook_test.py index c318346..7891eac 100644 --- a/tests/debug_statement_hook_test.py +++ b/tests/debug_statement_hook_test.py @@ -46,7 +46,7 @@ def test_returns_one_form_1(ast_with_debug_import_form_1): visitor = ImportStatementParser() visitor.visit(ast_with_debug_import_form_1) assert visitor.debug_import_statements == [ - DebugStatement('ipdb', 3, 0) + DebugStatement('ipdb', 3, 0), ] @@ -54,7 +54,7 @@ def test_returns_one_form_2(ast_with_debug_import_form_2): visitor = ImportStatementParser() visitor.visit(ast_with_debug_import_form_2) assert visitor.debug_import_statements == [ - DebugStatement('pudb', 3, 0) + DebugStatement('pudb', 3, 0), ] diff --git a/tests/detect_aws_credentials_test.py b/tests/detect_aws_credentials_test.py index 943a3f8..beb382f 100644 --- a/tests/detect_aws_credentials_test.py +++ b/tests/detect_aws_credentials_test.py @@ -21,14 +21,14 @@ from testing.util import get_resource_path ( { 'AWS_DUMMY_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar', - 'AWS_CREDENTIAL_FILE': '/baz' + 'AWS_CREDENTIAL_FILE': '/baz', }, {'/bar', '/baz'} ), ( { 'AWS_CONFIG_FILE': '/foo', 'AWS_CREDENTIAL_FILE': '/bar', - 'AWS_SHARED_CREDENTIALS_FILE': '/baz' + 'AWS_SHARED_CREDENTIALS_FILE': '/baz', }, {'/foo', '/bar', '/baz'} ), @@ -51,7 +51,7 @@ def test_get_aws_credentials_file_from_env(env_vars, values): ({'AWS_DUMMY_KEY': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar'}, {'bar'}), ( {'AWS_SECRET_ACCESS_KEY': 'foo', 'AWS_SECURITY_TOKEN': 'bar'}, - {'foo', 'bar'} + {'foo', 'bar'}, ), ), ) @@ -66,7 +66,7 @@ def test_get_aws_secrets_from_env(env_vars, values): ( ( 'aws_config_with_secret.ini', - {'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb'} + {'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb'}, ), ('aws_config_with_session_token.ini', {'foo'}), ('aws_config_with_secret_and_session_token.ini', @@ -77,8 +77,8 @@ def test_get_aws_secrets_from_env(env_vars, values): '7xebzorgm5143ouge9gvepxb2z70bsb2rtrh099e', 'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb', 'ixswosj8gz3wuik405jl9k3vdajsnxfhnpui38ez', - 'foo' - } + 'foo', + }, ), ('aws_config_without_secrets.ini', set()), ('nonsense.txt', set()), @@ -121,7 +121,7 @@ def test_non_existent_credentials(mock_secrets_env, mock_secrets_file, capsys): mock_secrets_file.return_value = set() ret = main(( get_resource_path('aws_config_without_secrets.ini'), - "--credentials-file=testing/resources/credentailsfilethatdoesntexist" + "--credentials-file=testing/resources/credentailsfilethatdoesntexist", )) assert ret == 2 out, _ = capsys.readouterr() @@ -141,6 +141,6 @@ def test_non_existent_credentials_with_allow_flag(mock_secrets_env, mock_secrets ret = main(( get_resource_path('aws_config_without_secrets.ini'), "--credentials-file=testing/resources/credentailsfilethatdoesntexist", - "--allow-missing-credentials" + "--allow-missing-credentials", )) assert ret == 0 diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py index 2c85c8a..1f9a14b 100644 --- a/tests/file_contents_sorter_test.py +++ b/tests/file_contents_sorter_test.py @@ -21,7 +21,7 @@ from pre_commit_hooks.file_contents_sorter import PASS (b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'), (b'extra\n\n\nwhitespace\n', FAIL, b'extra\nwhitespace\n'), (b'whitespace\n\n\nextra\n', FAIL, b'extra\nwhitespace\n'), - ) + ), ) def test_integration(input_s, expected_retval, output, tmpdir): path = tmpdir.join('file.txt') diff --git a/tests/fix_encoding_pragma_test.py b/tests/fix_encoding_pragma_test.py index d49f1ba..7288bfa 100644 --- a/tests/fix_encoding_pragma_test.py +++ b/tests/fix_encoding_pragma_test.py @@ -56,7 +56,7 @@ def test_integration_remove_ok(tmpdir): b'# -*- coding: utf-8 -*-\n' b'foo = "bar"\n' ), - ) + ), ) def test_ok_inputs(input_str): bytesio = io.BytesIO(input_str) @@ -100,7 +100,7 @@ def test_ok_inputs(input_str): (b'#!/usr/bin/env python\n', b''), (b'#!/usr/bin/env python\n#coding: utf8\n', b''), (b'#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n', b''), - ) + ), ) def test_not_ok_inputs(input_str, output): bytesio = io.BytesIO(input_str) diff --git a/tests/requirements_txt_fixer_test.py b/tests/requirements_txt_fixer_test.py index 3681cc6..dcf7a76 100644 --- a/tests/requirements_txt_fixer_test.py +++ b/tests/requirements_txt_fixer_test.py @@ -24,9 +24,9 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement ( b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n', FAIL, - b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n' + b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n', ), - ) + ), ) def test_integration(input_s, expected_retval, output, tmpdir): path = tmpdir.join('file.txt') diff --git a/tests/trailing_whitespace_fixer_test.py b/tests/trailing_whitespace_fixer_test.py index eb2a1d0..a771e67 100644 --- a/tests/trailing_whitespace_fixer_test.py +++ b/tests/trailing_whitespace_fixer_test.py @@ -69,7 +69,7 @@ def test_markdown_linebreak_ext_opt(filename, input_s, output, tmpdir): path = tmpdir.join(filename) path.write(input_s) ret = fix_trailing_whitespace(( - '--markdown-linebreak-ext=TxT', path.strpath + '--markdown-linebreak-ext=TxT', path.strpath, )) assert ret == 1 assert path.read() == output From e9aea74a77288f01d9656fd47a1bf9f8f3bc5a15 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2017 12:56:51 -0700 Subject: [PATCH 035/114] Upgrade add-trailing-comma to 0.4.1 --- .pre-commit-config.yaml | 2 +- pre_commit_hooks/no_commit_to_branch.py | 3 +- tests/check_executables_have_shebangs_test.py | 30 +++++++------ tests/check_json_test.py | 12 ++--- tests/check_symlinks_test.py | 10 +++-- tests/check_xml_test.py | 10 +++-- tests/check_yaml_test.py | 10 +++-- tests/debug_statement_hook_test.py | 18 +++++--- tests/detect_aws_credentials_test.py | 6 ++- tests/pretty_format_json_test.py | 44 +++++++++++-------- tests/string_fixer_test.py | 12 +++-- 11 files changed, 94 insertions(+), 63 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9467acf..9c48896 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,6 @@ hooks: - id: pyupgrade - repo: https://github.com/asottile/add-trailing-comma - sha: v0.3.0 + sha: v0.4.1 hooks: - id: add-trailing-comma diff --git a/pre_commit_hooks/no_commit_to_branch.py b/pre_commit_hooks/no_commit_to_branch.py index 4aa3535..22ee95e 100644 --- a/pre_commit_hooks/no_commit_to_branch.py +++ b/pre_commit_hooks/no_commit_to_branch.py @@ -15,7 +15,8 @@ def is_on_branch(protected): def main(argv=[]): parser = argparse.ArgumentParser() parser.add_argument( - '-b', '--branch', default='master', help='branch to disallow commits to') + '-b', '--branch', default='master', help='branch to disallow commits to', + ) args = parser.parse_args(argv) return int(is_on_branch(args.branch)) diff --git a/tests/check_executables_have_shebangs_test.py b/tests/check_executables_have_shebangs_test.py index 0e28986..0cb9dcf 100644 --- a/tests/check_executables_have_shebangs_test.py +++ b/tests/check_executables_have_shebangs_test.py @@ -7,26 +7,30 @@ import pytest from pre_commit_hooks.check_executables_have_shebangs import main -@pytest.mark.parametrize('content', ( - b'#!/bin/bash\nhello world\n', - b'#!/usr/bin/env python3.6', - b'#!python', - '#!☃'.encode('UTF-8'), -)) +@pytest.mark.parametrize( + 'content', ( + b'#!/bin/bash\nhello world\n', + b'#!/usr/bin/env python3.6', + b'#!python', + '#!☃'.encode('UTF-8'), + ), +) def test_has_shebang(content, tmpdir): path = tmpdir.join('path') path.write(content, 'wb') assert main((path.strpath,)) == 0 -@pytest.mark.parametrize('content', ( - b'', - b' #!python\n', - b'\n#!python\n', - b'python\n', - '☃'.encode('UTF-8'), +@pytest.mark.parametrize( + 'content', ( + b'', + b' #!python\n', + b'\n#!python\n', + b'python\n', + '☃'.encode('UTF-8'), -)) + ), +) def test_bad_shebang(content, tmpdir, capsys): path = tmpdir.join('path') path.write(content, 'wb') diff --git a/tests/check_json_test.py b/tests/check_json_test.py index 996bfbb..6ba26c1 100644 --- a/tests/check_json_test.py +++ b/tests/check_json_test.py @@ -4,11 +4,13 @@ from pre_commit_hooks.check_json import check_json from testing.util import get_resource_path -@pytest.mark.parametrize(('filename', 'expected_retval'), ( - ('bad_json.notjson', 1), - ('bad_json_latin1.nonjson', 1), - ('ok_json.json', 0), -)) +@pytest.mark.parametrize( + ('filename', 'expected_retval'), ( + ('bad_json.notjson', 1), + ('bad_json_latin1.nonjson', 1), + ('ok_json.json', 0), + ), +) def test_check_json(capsys, filename, expected_retval): ret = check_json([get_resource_path(filename)]) assert ret == expected_retval diff --git a/tests/check_symlinks_test.py b/tests/check_symlinks_test.py index 4b11e71..19bb5b4 100644 --- a/tests/check_symlinks_test.py +++ b/tests/check_symlinks_test.py @@ -7,10 +7,12 @@ from testing.util import get_resource_path @pytest.mark.xfail(os.name == 'nt', reason='No symlink support on windows') -@pytest.mark.parametrize(('filename', 'expected_retval'), ( - ('broken_symlink', 1), - ('working_symlink', 0), -)) +@pytest.mark.parametrize( + ('filename', 'expected_retval'), ( + ('broken_symlink', 1), + ('working_symlink', 0), + ), +) def test_check_symlinks(filename, expected_retval): ret = check_symlinks([get_resource_path(filename)]) assert ret == expected_retval diff --git a/tests/check_xml_test.py b/tests/check_xml_test.py index 99b0902..84e365d 100644 --- a/tests/check_xml_test.py +++ b/tests/check_xml_test.py @@ -4,10 +4,12 @@ from pre_commit_hooks.check_xml import check_xml from testing.util import get_resource_path -@pytest.mark.parametrize(('filename', 'expected_retval'), ( - ('bad_xml.notxml', 1), - ('ok_xml.xml', 0), -)) +@pytest.mark.parametrize( + ('filename', 'expected_retval'), ( + ('bad_xml.notxml', 1), + ('ok_xml.xml', 0), + ), +) def test_check_xml(filename, expected_retval): ret = check_xml([get_resource_path(filename)]) assert ret == expected_retval diff --git a/tests/check_yaml_test.py b/tests/check_yaml_test.py index c145fdc..73d6593 100644 --- a/tests/check_yaml_test.py +++ b/tests/check_yaml_test.py @@ -4,10 +4,12 @@ from pre_commit_hooks.check_yaml import check_yaml from testing.util import get_resource_path -@pytest.mark.parametrize(('filename', 'expected_retval'), ( - ('bad_yaml.notyaml', 1), - ('ok_yaml.yaml', 0), -)) +@pytest.mark.parametrize( + ('filename', 'expected_retval'), ( + ('bad_yaml.notyaml', 1), + ('ok_yaml.yaml', 0), + ), +) def test_check_yaml(filename, expected_retval): ret = check_yaml([get_resource_path(filename)]) assert ret == expected_retval diff --git a/tests/debug_statement_hook_test.py b/tests/debug_statement_hook_test.py index 7891eac..6d8d7d8 100644 --- a/tests/debug_statement_hook_test.py +++ b/tests/debug_statement_hook_test.py @@ -10,30 +10,36 @@ from testing.util import get_resource_path @pytest.fixture def ast_with_no_debug_imports(): - return ast.parse(""" + return ast.parse( + """ import foo import bar import baz from foo import bar -""") +""", + ) @pytest.fixture def ast_with_debug_import_form_1(): - return ast.parse(""" + return ast.parse( + """ import ipdb; ipdb.set_trace() -""") +""", + ) @pytest.fixture def ast_with_debug_import_form_2(): - return ast.parse(""" + return ast.parse( + """ from pudb import set_trace; set_trace() -""") +""", + ) def test_returns_no_debug_statements(ast_with_no_debug_imports): diff --git a/tests/detect_aws_credentials_test.py b/tests/detect_aws_credentials_test.py index beb382f..978e9b0 100644 --- a/tests/detect_aws_credentials_test.py +++ b/tests/detect_aws_credentials_test.py @@ -69,8 +69,10 @@ def test_get_aws_secrets_from_env(env_vars, values): {'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb'}, ), ('aws_config_with_session_token.ini', {'foo'}), - ('aws_config_with_secret_and_session_token.ini', - {'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb', 'foo'}), + ( + 'aws_config_with_secret_and_session_token.ini', + {'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb', 'foo'}, + ), ( 'aws_config_with_multiple_sections.ini', { diff --git a/tests/pretty_format_json_test.py b/tests/pretty_format_json_test.py index 62e37f1..eeef65b 100644 --- a/tests/pretty_format_json_test.py +++ b/tests/pretty_format_json_test.py @@ -17,35 +17,41 @@ def test_parse_indent(): parse_indent('-2') -@pytest.mark.parametrize(('filename', 'expected_retval'), ( - ('not_pretty_formatted_json.json', 1), - ('unsorted_pretty_formatted_json.json', 1), - ('non_ascii_pretty_formatted_json.json', 1), - ('pretty_formatted_json.json', 0), -)) +@pytest.mark.parametrize( + ('filename', 'expected_retval'), ( + ('not_pretty_formatted_json.json', 1), + ('unsorted_pretty_formatted_json.json', 1), + ('non_ascii_pretty_formatted_json.json', 1), + ('pretty_formatted_json.json', 0), + ), +) def test_pretty_format_json(filename, expected_retval): ret = pretty_format_json([get_resource_path(filename)]) assert ret == expected_retval -@pytest.mark.parametrize(('filename', 'expected_retval'), ( - ('not_pretty_formatted_json.json', 1), - ('unsorted_pretty_formatted_json.json', 0), - ('non_ascii_pretty_formatted_json.json', 1), - ('pretty_formatted_json.json', 0), -)) +@pytest.mark.parametrize( + ('filename', 'expected_retval'), ( + ('not_pretty_formatted_json.json', 1), + ('unsorted_pretty_formatted_json.json', 0), + ('non_ascii_pretty_formatted_json.json', 1), + ('pretty_formatted_json.json', 0), + ), +) def test_unsorted_pretty_format_json(filename, expected_retval): ret = pretty_format_json(['--no-sort-keys', get_resource_path(filename)]) assert ret == expected_retval -@pytest.mark.parametrize(('filename', 'expected_retval'), ( - ('not_pretty_formatted_json.json', 1), - ('unsorted_pretty_formatted_json.json', 1), - ('non_ascii_pretty_formatted_json.json', 1), - ('pretty_formatted_json.json', 1), - ('tab_pretty_formatted_json.json', 0), -)) +@pytest.mark.parametrize( + ('filename', 'expected_retval'), ( + ('not_pretty_formatted_json.json', 1), + ('unsorted_pretty_formatted_json.json', 1), + ('non_ascii_pretty_formatted_json.json', 1), + ('pretty_formatted_json.json', 1), + ('tab_pretty_formatted_json.json', 0), + ), +) def test_tab_pretty_format_json(filename, expected_retval): ret = pretty_format_json(['--indent', '\t', get_resource_path(filename)]) assert ret == expected_retval diff --git a/tests/string_fixer_test.py b/tests/string_fixer_test.py index 0429b95..a65213b 100644 --- a/tests/string_fixer_test.py +++ b/tests/string_fixer_test.py @@ -22,16 +22,20 @@ TESTS = ( # Docstring ('""" Foo """', '""" Foo """', 0), ( - textwrap.dedent(""" + textwrap.dedent( + """ x = " \\ foo \\ "\n - """), - textwrap.dedent(""" + """, + ), + textwrap.dedent( + """ x = ' \\ foo \\ '\n - """), + """, + ), 1, ), ('"foo""bar"', "'foo''bar'", 1), From b281d8790689938b839333cbc3a0e83934f4af26 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 17 Jul 2017 17:41:44 -0700 Subject: [PATCH 036/114] Upgrade pre-commit hooks --- .pre-commit-config.yaml | 8 ++++---- tests/detect_aws_credentials_test.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9c48896..c7f7629 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,20 +13,20 @@ - id: requirements-txt-fixer - id: flake8 - repo: https://github.com/pre-commit/pre-commit - sha: v0.15.0 + sha: v0.15.2 hooks: - id: validate_config - id: validate_manifest - repo: https://github.com/asottile/reorder_python_imports - sha: v0.3.1 + sha: v0.3.5 hooks: - id: reorder-python-imports language_version: python2.7 - repo: https://github.com/asottile/pyupgrade - sha: v1.1.2 + sha: v1.1.3 hooks: - id: pyupgrade - repo: https://github.com/asottile/add-trailing-comma - sha: v0.4.1 + sha: v0.5.1 hooks: - id: add-trailing-comma diff --git a/tests/detect_aws_credentials_test.py b/tests/detect_aws_credentials_test.py index 978e9b0..954f3d8 100644 --- a/tests/detect_aws_credentials_test.py +++ b/tests/detect_aws_credentials_test.py @@ -23,14 +23,14 @@ from testing.util import get_resource_path 'AWS_DUMMY_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar', 'AWS_CREDENTIAL_FILE': '/baz', }, - {'/bar', '/baz'} + {'/bar', '/baz'}, ), ( { 'AWS_CONFIG_FILE': '/foo', 'AWS_CREDENTIAL_FILE': '/bar', 'AWS_SHARED_CREDENTIALS_FILE': '/baz', }, - {'/foo', '/bar', '/baz'} + {'/foo', '/bar', '/baz'}, ), ), ) From a6b1e98e1212da04a796ef4981f59f051eeed53e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 19 Jul 2017 11:24:01 -0700 Subject: [PATCH 037/114] Upgrade add-trailing-comma --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c7f7629..00bf908 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,6 @@ hooks: - id: pyupgrade - repo: https://github.com/asottile/add-trailing-comma - sha: v0.5.1 + sha: v0.6.1 hooks: - id: add-trailing-comma From 3effa55911d61225feca8b56f350711d2e6f2f60 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 21 Jul 2017 09:37:41 -0700 Subject: [PATCH 038/114] More strict regex for name-tests-test --- .pre-commit-hooks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 1aba741..4301317 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -196,7 +196,7 @@ description: This verifies that test files are named correctly entry: name-tests-test language: python - files: tests/.+\.py$ + files: ^tests/.+\.py$ - id: no-commit-to-branch name: "Don't commit to branch" entry: no-commit-to-branch From 0c5f040a7f557df74eb7792850bde51df5b9eb3c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 1 Jul 2017 21:24:34 -0700 Subject: [PATCH 039/114] Use new git lfs (broken due to lfs returning non-lfs files) --- get-git-lfs.py | 4 ++-- pre_commit_hooks/check_added_large_files.py | 21 ++++++--------------- tests/check_added_large_files_test.py | 4 ---- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/get-git-lfs.py b/get-git-lfs.py index c1f2197..48dd31e 100755 --- a/get-git-lfs.py +++ b/get-git-lfs.py @@ -8,9 +8,9 @@ from urllib.request import urlopen DOWNLOAD_PATH = ( 'https://github.com/github/git-lfs/releases/download/' - 'v1.1.0/git-lfs-linux-amd64-1.1.0.tar.gz' + 'v2.2.1/git-lfs-linux-amd64-2.2.1.tar.gz' ) -PATH_IN_TAR = 'git-lfs-1.1.0/git-lfs' +PATH_IN_TAR = 'git-lfs-2.2.1/git-lfs' DEST_PATH = '/tmp/git-lfs/git-lfs' DEST_DIR = os.path.dirname(DEST_PATH) diff --git a/pre_commit_hooks/check_added_large_files.py b/pre_commit_hooks/check_added_large_files.py index 193b910..2d06706 100644 --- a/pre_commit_hooks/check_added_large_files.py +++ b/pre_commit_hooks/check_added_large_files.py @@ -4,6 +4,7 @@ from __future__ import print_function from __future__ import unicode_literals import argparse +import json import math import os @@ -13,23 +14,13 @@ from pre_commit_hooks.util import cmd_output def lfs_files(): - try: # pragma: no cover (no git-lfs) - lines = cmd_output('git', 'lfs', 'status', '--porcelain').splitlines() + try: + # Introduced in git-lfs 2.2.0, first working in 2.2.1 + lfs_ret = cmd_output('git', 'lfs', 'status', '--json') except CalledProcessError: # pragma: no cover (with git-lfs) - lines = [] + lfs_ret = '{"files":{}}' - modes_and_fileparts = [ - (line[:3].strip(), line[3:].rpartition(' ')[0]) for line in lines - ] - - def to_file_part(mode, filepart): # pragma: no cover (no git-lfs) - assert mode in ('A', 'R') - return filepart if mode == 'A' else filepart.split(' -> ')[1] - - return { - to_file_part(mode, filepart) for mode, filepart in modes_and_fileparts - if mode in ('A', 'R') - } + return set(json.loads(lfs_ret)['files']) def find_large_added_files(filenames, maxkb): diff --git a/tests/check_added_large_files_test.py b/tests/check_added_large_files_test.py index 4a84463..06671d7 100644 --- a/tests/check_added_large_files_test.py +++ b/tests/check_added_large_files_test.py @@ -79,10 +79,6 @@ xfailif_no_gitlfs = pytest.mark.xfail( @xfailif_no_gitlfs def test_allows_gitlfs(temp_git_dir): # pragma: no cover with temp_git_dir.as_cwd(): - # Work around https://github.com/github/git-lfs/issues/913 - cmd_output( - 'git', 'commit', '--no-gpg-sign', '--allow-empty', '-m', 'foo', - ) cmd_output('git', 'lfs', 'install') temp_git_dir.join('f.py').write('a' * 10000) cmd_output('git', 'lfs', 'track', 'f.py') From 9581d50e79774678482d4fc2ee497b8d70032668 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 26 Jul 2017 03:14:40 -0700 Subject: [PATCH 040/114] Workaround https://github.com/travis-ci/travis-ci/issues/6865 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a3e5fc4..c4c3b19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ matrix: - env: TOXENV=py36 python: 3.6 - env: TOXENV=pypy + python: pypy-5.7.1 install: pip install coveralls tox script: tox before_install: From eebb2e6d870a907d2e51f656f172a02f619dc83d Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 27 Jul 2017 15:09:31 -0700 Subject: [PATCH 041/114] Update README.md Fix error in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55b49ff..8113736 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,8 @@ Add this to your `.pre-commit-config.yaml` with single quoted strings. - `end-of-file-fixer` - Makes sure files end in a newline and only a newline. - `fix-encoding-pragma` - Add `# -*- coding: utf-8 -*-` to the top of python files. -- `file-contents-sorter` - Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input to it. Note that this hook WILL remove blank lines and does NOT respect any comments. - To remove the coding pragma pass `--remove` (useful in a python3-only codebase) +- `file-contents-sorter` - Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input to it. Note that this hook WILL remove blank lines and does NOT respect any comments. - `flake8` - Run flake8 on your python files. - `forbid-new-submodules` - Prevent addition of new git submodules. - `name-tests-test` - Assert that files in tests/ end in `_test.py`. From c5b56b63698a90f657cb348318f00214fad8787b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 30 Jul 2017 17:52:31 -0700 Subject: [PATCH 042/114] Use a better regex for name-tests-test --- .pre-commit-hooks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 4301317..69e316a 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -196,7 +196,7 @@ description: This verifies that test files are named correctly entry: name-tests-test language: python - files: ^tests/.+\.py$ + files: (^|/)tests/.+\.py$ - id: no-commit-to-branch name: "Don't commit to branch" entry: no-commit-to-branch From f36ce7025e428e7e398a7b92a3dcabc7d966f29a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 14 Aug 2017 18:24:29 -0700 Subject: [PATCH 043/114] Ran pre-commit autoupdate. Committed via https://github.com/asottile/all-repos --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00bf908..8583030 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ - id: requirements-txt-fixer - id: flake8 - repo: https://github.com/pre-commit/pre-commit - sha: v0.15.2 + sha: v0.16.3 hooks: - id: validate_config - id: validate_manifest @@ -23,10 +23,10 @@ - id: reorder-python-imports language_version: python2.7 - repo: https://github.com/asottile/pyupgrade - sha: v1.1.3 + sha: v1.1.4 hooks: - id: pyupgrade - repo: https://github.com/asottile/add-trailing-comma - sha: v0.6.1 + sha: v0.6.4 hooks: - id: add-trailing-comma From 2913408db3df938456fb71a5aacb21d967bbed8d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 14 Aug 2017 20:20:32 -0700 Subject: [PATCH 044/114] debug statements hook works for non-utf8 files --- pre_commit_hooks/debug_statement_hook.py | 2 +- tests/debug_statement_hook_test.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pre_commit_hooks/debug_statement_hook.py b/pre_commit_hooks/debug_statement_hook.py index d76e6e6..c5ca387 100644 --- a/pre_commit_hooks/debug_statement_hook.py +++ b/pre_commit_hooks/debug_statement_hook.py @@ -35,7 +35,7 @@ class ImportStatementParser(ast.NodeVisitor): def check_file_for_debug_statements(filename): try: - ast_obj = ast.parse(open(filename).read(), filename=filename) + ast_obj = ast.parse(open(filename, 'rb').read(), filename=filename) except SyntaxError: print('{} - Could not parse ast'.format(filename)) print() diff --git a/tests/debug_statement_hook_test.py b/tests/debug_statement_hook_test.py index 6d8d7d8..8832245 100644 --- a/tests/debug_statement_hook_test.py +++ b/tests/debug_statement_hook_test.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import unicode_literals + import ast import pytest @@ -77,3 +81,9 @@ def test_returns_zero_for_passing_file(): def test_syntaxerror_file(): ret = debug_statement_hook([get_resource_path('cannot_parse_ast.notpy')]) assert ret == 1 + + +def test_non_utf8_file(tmpdir): + f_py = tmpdir.join('f.py') + f_py.write_binary('# -*- coding: cp1252 -*-\nx = "€"\n'.encode('cp1252')) + assert debug_statement_hook((f_py.strpath,)) == 0 From 78dffcc819dede287b235299bd05694e6e30eb46 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Aug 2017 10:58:25 -0700 Subject: [PATCH 045/114] v0.9.2 --- .pre-commit-config.yaml | 2 +- CHANGELOG | 9 +++++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8583030..24c1859 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v0.9.1 + sha: v0.9.2 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG b/CHANGELOG index 688ef2b..c94ae92 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +0.9.2 +===== +- Report full python version in `check-ast`. +- Apply a more strict regular expression for `name-tests-test` +- Upgrade binding for `git-lfs` for `check-added-large-files`. The oldest + version that is supported is 2.2.1 (2.2.0 will incorrectly refer to all + files as "lfs" (false negative) and earlier versions will crash. +- `debug-statements` now works for non-utf-8 files. + 0.9.1 ===== - Add `check-executables-have-shebangs` hook. diff --git a/README.md b/README.md index 8113736..7b4c486 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v0.9.1 # Use the ref you want to point at + sha: v0.9.2 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 90ef3c6..4c8c148 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='0.9.1', + version='0.9.2', author='Anthony Sottile', author_email='asottile@umich.edu', From fc8a5b27e9ec9e3022587a57a5e55b91cf0ee83b Mon Sep 17 00:00:00 2001 From: Morgan Courbet Date: Tue, 13 Jun 2017 21:38:14 +0200 Subject: [PATCH 046/114] Add mixed-line-ending hook --- .pre-commit-hooks.yaml | 9 ++ README.md | 5 + hooks.yaml | 6 + pre_commit_hooks/mixed_line_ending.py | 212 ++++++++++++++++++++++++++ setup.py | 2 + tests/mixed_line_ending_test.py | 154 +++++++++++++++++++ 6 files changed, 388 insertions(+) create mode 100644 pre_commit_hooks/mixed_line_ending.py create mode 100644 tests/mixed_line_ending_test.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 69e316a..454f258 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -191,6 +191,15 @@ # for backward compatibility files: '' minimum_pre_commit_version: 0.15.0 +- id: mixed-line-ending + name: Mixed line ending + description: Replaces or checks mixed line ending + entry: mixed-line-ending + language: python + types: [text] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: name-tests-test name: Tests should end in _test.py description: This verifies that test files are named correctly diff --git a/README.md b/README.md index 7b4c486..8efd74b 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,11 @@ Add this to your `.pre-commit-config.yaml` - `file-contents-sorter` - Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input to it. Note that this hook WILL remove blank lines and does NOT respect any comments. - `flake8` - Run flake8 on your python files. - `forbid-new-submodules` - Prevent addition of new git submodules. +- `mixed-line-ending` - Replaces or checks mixed line ending. + - `--fix={auto,crlf,lf,no}` + - `auto` - Replaces automatically the most frequent line ending. This is the default argument. + - `crlf`, `lf` - Forces to replace line ending by respectively CRLF and LF. + - `no` - Checks if there is any mixed line ending without modifying any file. - `name-tests-test` - Assert that files in tests/ end in `_test.py`. - Use `args: ['--django']` to match `test*.py` instead. - `no-commit-to-branch` - Protect specific branches from direct checkins. diff --git a/hooks.yaml b/hooks.yaml index 5278bf5..59cc320 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -130,6 +130,12 @@ entry: upgrade-your-pre-commit-version files: '' minimum_pre_commit_version: 0.15.0 +- id: mixed-line-ending + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: name-tests-test language: system name: upgrade-your-pre-commit-version diff --git a/pre_commit_hooks/mixed_line_ending.py b/pre_commit_hooks/mixed_line_ending.py new file mode 100644 index 0000000..76512a7 --- /dev/null +++ b/pre_commit_hooks/mixed_line_ending.py @@ -0,0 +1,212 @@ +import argparse +import re +import sys + +from enum import Enum + + +class LineEnding(Enum): + CR = b'\r', 'cr', re.compile(b'\r(?!\n)', re.DOTALL) + CRLF = b'\r\n', 'crlf', re.compile(b'\r\n', re.DOTALL) + LF = b'\n', 'lf', re.compile(b'(? 0 + + mixed |= le_found_previously and le_found_cur + le_found_previously |= le_found_cur + + if le_count == max_le_count: + most_le = None + elif le_count > max_le_count: + max_le_count = le_count + most_le = le + + if not mixed: + return MixedLineDetection.NOT_MIXED + + for mld in MixedLineDetection: + if ( + mld.line_ending_enum is not None and + mld.line_ending_enum == most_le + ): + return mld + + return MixedLineDetection.UNKNOWN + + +def _process_no_fix(filenames): + print('Checking if the files have mixed line ending.') + + mle_filenames = [] + for filename in filenames: + detect_result = _detect_line_ending(filename) + + if detect_result.mle_found: + mle_filenames.append(filename) + + mle_found = len(mle_filenames) > 0 + + if mle_found: + print( + 'The following files have mixed line endings:\n\t%s', + '\n\t'.join(mle_filenames), + ) + + return 1 if mle_found else 0 + + +def _process_fix_auto(filenames): + mle_found = False + + for filename in filenames: + detect_result = _detect_line_ending(filename) + + if detect_result == MixedLineDetection.NOT_MIXED: + print('The file %s has no mixed line ending', filename) + elif detect_result == MixedLineDetection.UNKNOWN: + print( + 'Could not define most frequent line ending in ' + 'file %s. File skiped.', filename, + ) + + mle_found = True + else: + le_enum = detect_result.line_ending_enum + + print( + 'The file %s has mixed line ending with a ' + 'majority of %s. Converting...', filename, le_enum.str_print, + ) + + _convert_line_ending(filename, le_enum.string) + mle_found = True + + print( + 'The file %s has been converted to %s line ending.', + filename, le_enum.str_print, + ) + + return 1 if mle_found else 0 + + +def _process_fix_force(filenames, line_ending_enum): + for filename in filenames: + _convert_line_ending(filename, line_ending_enum.string) + + print( + 'The file %s has been forced to %s line ending.', + filename, line_ending_enum.str_print, + ) + + return 1 + + +def _convert_line_ending(filename, line_ending): + with open(filename, 'rb+') as f: + bufin = f.read() + + # convert line ending + bufout = ANY_LINE_ENDING_PATTERN.sub(line_ending, bufin) + + # write the result in the file replacing the existing content + f.seek(0) + f.write(bufout) + f.truncate() + + +if __name__ == '__main__': + sys.exit(mixed_line_ending()) diff --git a/setup.py b/setup.py index 4c8c148..432c19a 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ setup( 'simplejson', 'six', ], + extras_require={':python_version=="2.7"': ['enum34']}, entry_points={ 'console_scripts': [ 'autopep8-wrapper = pre_commit_hooks.autopep8_wrapper:main', @@ -53,6 +54,7 @@ setup( 'file-contents-sorter = pre_commit_hooks.file_contents_sorter:main', 'fix-encoding-pragma = pre_commit_hooks.fix_encoding_pragma:main', 'forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main', + 'mixed-line-ending = pre_commit_hooks.mixed_line_ending:mixed_line_ending', 'name-tests-test = pre_commit_hooks.tests_should_end_in_test:validate_files', 'no-commit-to-branch = pre_commit_hooks.no_commit_to_branch:main', 'pretty-format-json = pre_commit_hooks.pretty_format_json:pretty_format_json', diff --git a/tests/mixed_line_ending_test.py b/tests/mixed_line_ending_test.py new file mode 100644 index 0000000..9b49b5e --- /dev/null +++ b/tests/mixed_line_ending_test.py @@ -0,0 +1,154 @@ +import pytest + +from pre_commit_hooks.mixed_line_ending import mixed_line_ending + +# Input, expected return value, expected output +TESTS_FIX_AUTO = ( + # only 'LF' + (b'foo\nbar\nbaz\n', 0, b'foo\nbar\nbaz\n'), + # only 'CRLF' + (b'foo\r\nbar\r\nbaz\r\n', 0, b'foo\r\nbar\r\nbaz\r\n'), + # only 'CR' + (b'foo\rbar\rbaz\r', 0, b'foo\rbar\rbaz\r'), + # mixed with majority of 'LF' + (b'foo\r\nbar\nbaz\n', 1, b'foo\nbar\nbaz\n'), + # mixed with majority of 'CRLF' + (b'foo\r\nbar\nbaz\r\n', 1, b'foo\r\nbar\r\nbaz\r\n'), + # mixed with majority of 'CR' + (b'foo\rbar\nbaz\r', 1, b'foo\rbar\rbaz\r'), + # mixed with as much 'LF' as 'CRLF' + (b'foo\r\nbar\nbaz', 1, b'foo\r\nbar\nbaz'), + # mixed with as much 'LF' as 'CR' + (b'foo\rbar\nbaz', 1, b'foo\rbar\nbaz'), + # mixed with as much 'CRLF' as 'CR' + (b'foo\r\nbar\nbaz', 1, b'foo\r\nbar\nbaz'), + # mixed with as much 'CRLF' as 'LF' as 'CR' + (b'foo\r\nbar\nbaz\r', 1, b'foo\r\nbar\nbaz\r'), +) + + +@pytest.mark.parametrize( + ('input_s', 'expected_retval', 'output'), + TESTS_FIX_AUTO, +) +def test_mixed_line_ending_fix_auto(input_s, expected_retval, output, tmpdir): + path = tmpdir.join('file.txt') + path.write(input_s) + ret = mixed_line_ending(('--fix=auto', path.strpath)) + + assert ret == expected_retval + assert path.read_binary() == output + + +# Input, expected return value, expected output +TESTS_NO_FIX = ( + # only 'LF' + (b'foo\nbar\nbaz\n', 0, b'foo\nbar\nbaz\n'), + # only 'CRLF' + (b'foo\r\nbar\r\nbaz\r\n', 0, b'foo\r\nbar\r\nbaz\r\n'), + # only 'CR' + (b'foo\rbar\rbaz\r', 0, b'foo\rbar\rbaz\r'), + # mixed with majority of 'LF' + (b'foo\r\nbar\nbaz\n', 1, b'foo\r\nbar\nbaz\n'), + # mixed with majority of 'CRLF' + (b'foo\r\nbar\nbaz\r\n', 1, b'foo\r\nbar\nbaz\r\n'), + # mixed with majority of 'CR' + (b'foo\rbar\nbaz\r', 1, b'foo\rbar\nbaz\r'), + # mixed with as much 'LF' as 'CR' + (b'foo\rbar\nbaz', 0, b'foo\rbar\nbaz'), + # mixed with as much 'CRLF' as 'CR' + (b'foo\r\nbar\nbaz', 0, b'foo\r\nbar\nbaz'), + # mixed with as much 'CRLF' as 'LF' as 'CR' + (b'foo\r\nbar\nbaz\r', 0, b'foo\r\nbar\nbaz\r'), +) + + +@pytest.mark.parametrize( + ('input_s', 'expected_retval', 'output'), + TESTS_NO_FIX, +) +def test_detect_mixed_line_ending(input_s, expected_retval, output, tmpdir): + path = tmpdir.join('file.txt') + path.write(input_s) + ret = mixed_line_ending(('--fix=no', path.strpath)) + + assert ret == expected_retval + assert path.read_binary() == output + + +# Input, expected return value, expected output +TESTS_FIX_FORCE_LF = ( + # only 'LF' + (b'foo\nbar\nbaz\n', 1, b'foo\nbar\nbaz\n'), + # only 'CRLF' + (b'foo\r\nbar\r\nbaz\r\n', 1, b'foo\nbar\nbaz\n'), + # only 'CR' + (b'foo\rbar\rbaz\r', 1, b'foo\nbar\nbaz\n'), + # mixed with majority of 'LF' + (b'foo\r\nbar\nbaz\n', 1, b'foo\nbar\nbaz\n'), + # mixed with majority of 'CRLF' + (b'foo\r\nbar\nbaz\r\n', 1, b'foo\nbar\nbaz\n'), + # mixed with majority of 'CR' + (b'foo\rbar\nbaz\r', 1, b'foo\nbar\nbaz\n'), + # mixed with as much 'LF' as 'CR' + (b'foo\rbar\nbaz', 1, b'foo\nbar\nbaz'), + # mixed with as much 'CRLF' as 'CR' + (b'foo\r\nbar\nbaz', 1, b'foo\nbar\nbaz'), + # mixed with as much 'CRLF' as 'LF' as 'CR' + (b'foo\r\nbar\nbaz\r', 1, b'foo\nbar\nbaz\n'), +) + + +@pytest.mark.parametrize( + ('input_s', 'expected_retval', 'output'), + TESTS_FIX_FORCE_LF, +) +def test_mixed_line_ending_fix_force_lf( + input_s, expected_retval, output, + tmpdir, +): + path = tmpdir.join('file.txt') + path.write(input_s) + ret = mixed_line_ending(('--fix=lf', path.strpath)) + + assert ret == expected_retval + assert path.read_binary() == output + + +# Input, expected return value, expected output +TESTS_FIX_FORCE_CRLF = ( + # only 'LF' + (b'foo\nbar\nbaz\n', 1, b'foo\r\nbar\r\nbaz\r\n'), + # only 'CRLF' + (b'foo\r\nbar\r\nbaz\r\n', 1, b'foo\r\nbar\r\nbaz\r\n'), + # only 'CR' + (b'foo\rbar\rbaz\r', 1, b'foo\r\nbar\r\nbaz\r\n'), + # mixed with majority of 'LF' + (b'foo\r\nbar\nbaz\n', 1, b'foo\r\nbar\r\nbaz\r\n'), + # mixed with majority of 'CRLF' + (b'foo\r\nbar\nbaz\r\n', 1, b'foo\r\nbar\r\nbaz\r\n'), + # mixed with majority of 'CR' + (b'foo\rbar\nbaz\r', 1, b'foo\r\nbar\r\nbaz\r\n'), + # mixed with as much 'LF' as 'CR' + (b'foo\rbar\nbaz', 1, b'foo\r\nbar\r\nbaz'), + # mixed with as much 'CRLF' as 'CR' + (b'foo\r\nbar\nbaz', 1, b'foo\r\nbar\r\nbaz'), + # mixed with as much 'CRLF' as 'LF' as 'CR' + (b'foo\r\nbar\nbaz\r', 1, b'foo\r\nbar\r\nbaz\r\n'), +) + + +@pytest.mark.parametrize( + ('input_s', 'expected_retval', 'output'), + TESTS_FIX_FORCE_CRLF, +) +def test_mixed_line_ending_fix_force_crlf( + input_s, expected_retval, output, + tmpdir, +): + path = tmpdir.join('file.txt') + path.write(input_s) + ret = mixed_line_ending(('--fix=crlf', path.strpath)) + + assert ret == expected_retval + assert path.read_binary() == output From 47c4d9ebedb4760b84c15f504da8b9b04a8833f5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 5 Sep 2017 19:27:34 -0700 Subject: [PATCH 047/114] Fix mixed-line-ending tests on windows --- tests/mixed_line_ending_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/mixed_line_ending_test.py b/tests/mixed_line_ending_test.py index 9b49b5e..20fd22c 100644 --- a/tests/mixed_line_ending_test.py +++ b/tests/mixed_line_ending_test.py @@ -33,7 +33,7 @@ TESTS_FIX_AUTO = ( ) def test_mixed_line_ending_fix_auto(input_s, expected_retval, output, tmpdir): path = tmpdir.join('file.txt') - path.write(input_s) + path.write_binary(input_s) ret = mixed_line_ending(('--fix=auto', path.strpath)) assert ret == expected_retval @@ -69,7 +69,7 @@ TESTS_NO_FIX = ( ) def test_detect_mixed_line_ending(input_s, expected_retval, output, tmpdir): path = tmpdir.join('file.txt') - path.write(input_s) + path.write_binary(input_s) ret = mixed_line_ending(('--fix=no', path.strpath)) assert ret == expected_retval @@ -108,7 +108,7 @@ def test_mixed_line_ending_fix_force_lf( tmpdir, ): path = tmpdir.join('file.txt') - path.write(input_s) + path.write_binary(input_s) ret = mixed_line_ending(('--fix=lf', path.strpath)) assert ret == expected_retval @@ -147,7 +147,7 @@ def test_mixed_line_ending_fix_force_crlf( tmpdir, ): path = tmpdir.join('file.txt') - path.write(input_s) + path.write_binary(input_s) ret = mixed_line_ending(('--fix=crlf', path.strpath)) assert ret == expected_retval From fbcd096ea91195af3b47a99a9c7799203231db3d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 5 Sep 2017 20:20:43 -0700 Subject: [PATCH 048/114] Simplify mixed-line-ending hook --- pre_commit_hooks/mixed_line_ending.py | 243 ++++++-------------------- setup.py | 1 - tests/mixed_line_ending_test.py | 205 ++++++++-------------- 3 files changed, 134 insertions(+), 315 deletions(-) diff --git a/pre_commit_hooks/mixed_line_ending.py b/pre_commit_hooks/mixed_line_ending.py index 76512a7..301c654 100644 --- a/pre_commit_hooks/mixed_line_ending.py +++ b/pre_commit_hooks/mixed_line_ending.py @@ -1,212 +1,83 @@ +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + import argparse -import re -import sys - -from enum import Enum +import collections -class LineEnding(Enum): - CR = b'\r', 'cr', re.compile(b'\r(?!\n)', re.DOTALL) - CRLF = b'\r\n', 'crlf', re.compile(b'\r\n', re.DOTALL) - LF = b'\n', 'lf', re.compile(b'(? 1 + if fix == 'no' or (fix == 'auto' and not mixed): + return mixed -def mixed_line_ending(argv=None): - options = _parse_arguments(argv) + if fix == 'auto': + max_ending = LF + max_lines = 0 + # ordering is important here such that lf > crlf > cr + for ending_type in ALL_ENDINGS: + # also important, using >= to find a max that prefers the last + if counts[ending_type] >= max_lines: + max_ending = ending_type + max_lines = counts[ending_type] - filenames = options['filenames'] - fix_option = options['fix'] - - if fix_option == MixedLineEndingOption.NO: - return _process_no_fix(filenames) - elif fix_option == MixedLineEndingOption.AUTO: - return _process_fix_auto(filenames) - # when a line ending character is forced with --fix option + _fix(filename, contents, max_ending) + return 1 else: - return _process_fix_force(filenames, fix_option.line_ending_enum) + target_ending = FIX_TO_LINE_ENDING[fix] + # find if there are lines with *other* endings + del counts[target_ending] + other_endings = bool(sum(counts.values())) + if other_endings: + _fix(filename, contents, target_ending) + return other_endings -def _parse_arguments(argv=None): +def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument( - '-f', - '--fix', - choices=[m.opt_name for m in MixedLineEndingOption], - default=MixedLineEndingOption.AUTO.opt_name, + '-f', '--fix', + choices=('auto', 'no') + tuple(FIX_TO_LINE_ENDING), + default='auto', help='Replace line ending with the specified. Default is "auto"', ) parser.add_argument('filenames', nargs='*', help='Filenames to fix') args = parser.parse_args(argv) - fix, = ( - member for name, member - in MixedLineEndingOption.__members__.items() - if member.opt_name == args.fix - ) - - options = { - 'fix': fix, 'filenames': args.filenames, - } - - return options - - -def _detect_line_ending(filename): - with open(filename, 'rb') as f: - buf = f.read() - - le_counts = {} - - for le_enum in LineEnding: - le_counts[le_enum] = len(le_enum.regex.findall(buf)) - - mixed = False - le_found_previously = False - most_le = None - max_le_count = 0 - - for le, le_count in le_counts.items(): - le_found_cur = le_count > 0 - - mixed |= le_found_previously and le_found_cur - le_found_previously |= le_found_cur - - if le_count == max_le_count: - most_le = None - elif le_count > max_le_count: - max_le_count = le_count - most_le = le - - if not mixed: - return MixedLineDetection.NOT_MIXED - - for mld in MixedLineDetection: - if ( - mld.line_ending_enum is not None and - mld.line_ending_enum == most_le - ): - return mld - - return MixedLineDetection.UNKNOWN - - -def _process_no_fix(filenames): - print('Checking if the files have mixed line ending.') - - mle_filenames = [] - for filename in filenames: - detect_result = _detect_line_ending(filename) - - if detect_result.mle_found: - mle_filenames.append(filename) - - mle_found = len(mle_filenames) > 0 - - if mle_found: - print( - 'The following files have mixed line endings:\n\t%s', - '\n\t'.join(mle_filenames), - ) - - return 1 if mle_found else 0 - - -def _process_fix_auto(filenames): - mle_found = False - - for filename in filenames: - detect_result = _detect_line_ending(filename) - - if detect_result == MixedLineDetection.NOT_MIXED: - print('The file %s has no mixed line ending', filename) - elif detect_result == MixedLineDetection.UNKNOWN: - print( - 'Could not define most frequent line ending in ' - 'file %s. File skiped.', filename, - ) - - mle_found = True - else: - le_enum = detect_result.line_ending_enum - - print( - 'The file %s has mixed line ending with a ' - 'majority of %s. Converting...', filename, le_enum.str_print, - ) - - _convert_line_ending(filename, le_enum.string) - mle_found = True - - print( - 'The file %s has been converted to %s line ending.', - filename, le_enum.str_print, - ) - - return 1 if mle_found else 0 - - -def _process_fix_force(filenames, line_ending_enum): - for filename in filenames: - _convert_line_ending(filename, line_ending_enum.string) - - print( - 'The file %s has been forced to %s line ending.', - filename, line_ending_enum.str_print, - ) - - return 1 - - -def _convert_line_ending(filename, line_ending): - with open(filename, 'rb+') as f: - bufin = f.read() - - # convert line ending - bufout = ANY_LINE_ENDING_PATTERN.sub(line_ending, bufin) - - # write the result in the file replacing the existing content - f.seek(0) - f.write(bufout) - f.truncate() + retv = 0 + for filename in args.filenames: + retv |= fix_filename(filename, args.fix) + return retv if __name__ == '__main__': - sys.exit(mixed_line_ending()) + exit(main()) diff --git a/setup.py b/setup.py index 432c19a..2563095 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,6 @@ setup( 'simplejson', 'six', ], - extras_require={':python_version=="2.7"': ['enum34']}, entry_points={ 'console_scripts': [ 'autopep8-wrapper = pre_commit_hooks.autopep8_wrapper:main', diff --git a/tests/mixed_line_ending_test.py b/tests/mixed_line_ending_test.py index 20fd22c..808295b 100644 --- a/tests/mixed_line_ending_test.py +++ b/tests/mixed_line_ending_test.py @@ -1,154 +1,103 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + import pytest -from pre_commit_hooks.mixed_line_ending import mixed_line_ending - -# Input, expected return value, expected output -TESTS_FIX_AUTO = ( - # only 'LF' - (b'foo\nbar\nbaz\n', 0, b'foo\nbar\nbaz\n'), - # only 'CRLF' - (b'foo\r\nbar\r\nbaz\r\n', 0, b'foo\r\nbar\r\nbaz\r\n'), - # only 'CR' - (b'foo\rbar\rbaz\r', 0, b'foo\rbar\rbaz\r'), - # mixed with majority of 'LF' - (b'foo\r\nbar\nbaz\n', 1, b'foo\nbar\nbaz\n'), - # mixed with majority of 'CRLF' - (b'foo\r\nbar\nbaz\r\n', 1, b'foo\r\nbar\r\nbaz\r\n'), - # mixed with majority of 'CR' - (b'foo\rbar\nbaz\r', 1, b'foo\rbar\rbaz\r'), - # mixed with as much 'LF' as 'CRLF' - (b'foo\r\nbar\nbaz', 1, b'foo\r\nbar\nbaz'), - # mixed with as much 'LF' as 'CR' - (b'foo\rbar\nbaz', 1, b'foo\rbar\nbaz'), - # mixed with as much 'CRLF' as 'CR' - (b'foo\r\nbar\nbaz', 1, b'foo\r\nbar\nbaz'), - # mixed with as much 'CRLF' as 'LF' as 'CR' - (b'foo\r\nbar\nbaz\r', 1, b'foo\r\nbar\nbaz\r'), -) +from pre_commit_hooks.mixed_line_ending import main @pytest.mark.parametrize( - ('input_s', 'expected_retval', 'output'), - TESTS_FIX_AUTO, + ('input_s', 'output'), + ( + # mixed with majority of 'LF' + (b'foo\r\nbar\nbaz\n', b'foo\nbar\nbaz\n'), + # mixed with majority of 'CRLF' + (b'foo\r\nbar\nbaz\r\n', b'foo\r\nbar\r\nbaz\r\n'), + # mixed with majority of 'CR' + (b'foo\rbar\nbaz\r', b'foo\rbar\rbaz\r'), + # mixed with as much 'LF' as 'CRLF' + (b'foo\r\nbar\n', b'foo\nbar\n'), + # mixed with as much 'LF' as 'CR' + (b'foo\rbar\n', b'foo\nbar\n'), + # mixed with as much 'CRLF' as 'CR' + (b'foo\r\nbar\r', b'foo\r\nbar\r\n'), + # mixed with as much 'CRLF' as 'LF' as 'CR' + (b'foo\r\nbar\nbaz\r', b'foo\nbar\nbaz\n'), + ), ) -def test_mixed_line_ending_fix_auto(input_s, expected_retval, output, tmpdir): +def test_mixed_line_ending_fixes_auto(input_s, output, tmpdir): path = tmpdir.join('file.txt') path.write_binary(input_s) - ret = mixed_line_ending(('--fix=auto', path.strpath)) + ret = main((path.strpath,)) - assert ret == expected_retval + assert ret == 1 assert path.read_binary() == output -# Input, expected return value, expected output -TESTS_NO_FIX = ( - # only 'LF' - (b'foo\nbar\nbaz\n', 0, b'foo\nbar\nbaz\n'), - # only 'CRLF' - (b'foo\r\nbar\r\nbaz\r\n', 0, b'foo\r\nbar\r\nbaz\r\n'), - # only 'CR' - (b'foo\rbar\rbaz\r', 0, b'foo\rbar\rbaz\r'), - # mixed with majority of 'LF' - (b'foo\r\nbar\nbaz\n', 1, b'foo\r\nbar\nbaz\n'), - # mixed with majority of 'CRLF' - (b'foo\r\nbar\nbaz\r\n', 1, b'foo\r\nbar\nbaz\r\n'), - # mixed with majority of 'CR' - (b'foo\rbar\nbaz\r', 1, b'foo\rbar\nbaz\r'), - # mixed with as much 'LF' as 'CR' - (b'foo\rbar\nbaz', 0, b'foo\rbar\nbaz'), - # mixed with as much 'CRLF' as 'CR' - (b'foo\r\nbar\nbaz', 0, b'foo\r\nbar\nbaz'), - # mixed with as much 'CRLF' as 'LF' as 'CR' - (b'foo\r\nbar\nbaz\r', 0, b'foo\r\nbar\nbaz\r'), -) +def test_non_mixed_no_newline_end_of_file(tmpdir): + path = tmpdir.join('f.txt') + path.write_binary(b'foo\nbar\nbaz') + assert not main((path.strpath,)) + # the hook *could* fix the end of the file, but leaves it alone + # this is mostly to document the current behaviour + assert path.read_binary() == b'foo\nbar\nbaz' + + +def test_mixed_no_newline_end_of_file(tmpdir): + path = tmpdir.join('f.txt') + path.write_binary(b'foo\r\nbar\nbaz') + assert main((path.strpath,)) + # the hook rewrites the end of the file, this is slightly inconsistent + # with the non-mixed case but I think this is the better behaviour + # this is mostly to document the current behaviour + assert path.read_binary() == b'foo\nbar\nbaz\n' @pytest.mark.parametrize( - ('input_s', 'expected_retval', 'output'), - TESTS_NO_FIX, + ('fix_option', 'input_s'), + ( + # All --fix=auto with uniform line endings should be ok + ('--fix=auto', b'foo\r\nbar\r\nbaz\r\n'), + ('--fix=auto', b'foo\rbar\rbaz\r'), + ('--fix=auto', b'foo\nbar\nbaz\n'), + # --fix=crlf with crlf endings + ('--fix=crlf', b'foo\r\nbar\r\nbaz\r\n'), + # --fix=lf with lf endings + ('--fix=lf', b'foo\nbar\nbaz\n'), + ), ) -def test_detect_mixed_line_ending(input_s, expected_retval, output, tmpdir): - path = tmpdir.join('file.txt') +def test_line_endings_ok(fix_option, input_s, tmpdir): + path = tmpdir.join('input.txt') path.write_binary(input_s) - ret = mixed_line_ending(('--fix=no', path.strpath)) + ret = main((fix_option, path.strpath)) - assert ret == expected_retval - assert path.read_binary() == output + assert ret == 0 + assert path.read_binary() == input_s -# Input, expected return value, expected output -TESTS_FIX_FORCE_LF = ( - # only 'LF' - (b'foo\nbar\nbaz\n', 1, b'foo\nbar\nbaz\n'), - # only 'CRLF' - (b'foo\r\nbar\r\nbaz\r\n', 1, b'foo\nbar\nbaz\n'), - # only 'CR' - (b'foo\rbar\rbaz\r', 1, b'foo\nbar\nbaz\n'), - # mixed with majority of 'LF' - (b'foo\r\nbar\nbaz\n', 1, b'foo\nbar\nbaz\n'), - # mixed with majority of 'CRLF' - (b'foo\r\nbar\nbaz\r\n', 1, b'foo\nbar\nbaz\n'), - # mixed with majority of 'CR' - (b'foo\rbar\nbaz\r', 1, b'foo\nbar\nbaz\n'), - # mixed with as much 'LF' as 'CR' - (b'foo\rbar\nbaz', 1, b'foo\nbar\nbaz'), - # mixed with as much 'CRLF' as 'CR' - (b'foo\r\nbar\nbaz', 1, b'foo\nbar\nbaz'), - # mixed with as much 'CRLF' as 'LF' as 'CR' - (b'foo\r\nbar\nbaz\r', 1, b'foo\nbar\nbaz\n'), -) +def test_no_fix_does_not_modify(tmpdir): + path = tmpdir.join('input.txt') + contents = b'foo\r\nbar\rbaz\nwomp\n' + path.write_binary(contents) + ret = main(('--fix=no', path.strpath)) + + assert ret == 1 + assert path.read_binary() == contents -@pytest.mark.parametrize( - ('input_s', 'expected_retval', 'output'), - TESTS_FIX_FORCE_LF, -) -def test_mixed_line_ending_fix_force_lf( - input_s, expected_retval, output, - tmpdir, -): - path = tmpdir.join('file.txt') - path.write_binary(input_s) - ret = mixed_line_ending(('--fix=lf', path.strpath)) +def test_fix_lf(tmpdir): + path = tmpdir.join('input.txt') + path.write_binary(b'foo\r\nbar\rbaz\n') + ret = main(('--fix=lf', path.strpath)) - assert ret == expected_retval - assert path.read_binary() == output + assert ret == 1 + assert path.read_binary() == b'foo\nbar\nbaz\n' -# Input, expected return value, expected output -TESTS_FIX_FORCE_CRLF = ( - # only 'LF' - (b'foo\nbar\nbaz\n', 1, b'foo\r\nbar\r\nbaz\r\n'), - # only 'CRLF' - (b'foo\r\nbar\r\nbaz\r\n', 1, b'foo\r\nbar\r\nbaz\r\n'), - # only 'CR' - (b'foo\rbar\rbaz\r', 1, b'foo\r\nbar\r\nbaz\r\n'), - # mixed with majority of 'LF' - (b'foo\r\nbar\nbaz\n', 1, b'foo\r\nbar\r\nbaz\r\n'), - # mixed with majority of 'CRLF' - (b'foo\r\nbar\nbaz\r\n', 1, b'foo\r\nbar\r\nbaz\r\n'), - # mixed with majority of 'CR' - (b'foo\rbar\nbaz\r', 1, b'foo\r\nbar\r\nbaz\r\n'), - # mixed with as much 'LF' as 'CR' - (b'foo\rbar\nbaz', 1, b'foo\r\nbar\r\nbaz'), - # mixed with as much 'CRLF' as 'CR' - (b'foo\r\nbar\nbaz', 1, b'foo\r\nbar\r\nbaz'), - # mixed with as much 'CRLF' as 'LF' as 'CR' - (b'foo\r\nbar\nbaz\r', 1, b'foo\r\nbar\r\nbaz\r\n'), -) +def test_fix_crlf(tmpdir): + path = tmpdir.join('input.txt') + path.write_binary(b'foo\r\nbar\rbaz\n') + ret = main(('--fix=crlf', path.strpath)) - -@pytest.mark.parametrize( - ('input_s', 'expected_retval', 'output'), - TESTS_FIX_FORCE_CRLF, -) -def test_mixed_line_ending_fix_force_crlf( - input_s, expected_retval, output, - tmpdir, -): - path = tmpdir.join('file.txt') - path.write_binary(input_s) - ret = mixed_line_ending(('--fix=crlf', path.strpath)) - - assert ret == expected_retval - assert path.read_binary() == output + assert ret == 1 + assert path.read_binary() == b'foo\r\nbar\r\nbaz\r\n' From d87f6cba306bfbc1829f800db0f1d49fc0fa8bb6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 7 Sep 2017 17:48:54 -0700 Subject: [PATCH 049/114] Remove unneeded validate_config pre-commit hook --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 24c1859..0104614 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,6 @@ - repo: https://github.com/pre-commit/pre-commit sha: v0.16.3 hooks: - - id: validate_config - id: validate_manifest - repo: https://github.com/asottile/reorder_python_imports sha: v0.3.5 From e577432d8bb24b0d6b81fe798b1811dfb8de89c0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 7 Sep 2017 19:22:03 -0700 Subject: [PATCH 050/114] Ran pre-commit migrate-config. Committed via https://github.com/asottile/all-repos --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0104614..5fed7da 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +repos: - repo: https://github.com/pre-commit/pre-commit-hooks sha: v0.9.2 hooks: From 8f1217bf9f19ef4ebf1ed1398692ee49f5c7778e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 7 Sep 2017 19:31:59 -0700 Subject: [PATCH 051/114] Update pre-commit cache directory. Committed via https://github.com/asottile/all-repos --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c4c3b19..6d428ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,5 +18,5 @@ after_success: coveralls cache: directories: - $HOME/.cache/pip - - $HOME/.pre-commit + - $HOME/.cache/pre-commit - /tmp/git-lfs diff --git a/appveyor.yml b/appveyor.yml index a0df40b..8df64a5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,4 +14,4 @@ test_script: tox cache: - '%LOCALAPPDATA%\pip\cache' - - '%USERPROFILE%\.pre-commit' + - '%USERPROFILE%\.cache\pre-commit' From 9730eb3beb235eabc57d188b58fd135065d07f20 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 7 Sep 2017 19:54:30 -0700 Subject: [PATCH 052/114] v0.9.3 --- .pre-commit-config.yaml | 2 +- CHANGELOG | 4 ++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5fed7da..07ab748 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v0.9.2 + sha: v0.9.3 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG b/CHANGELOG index c94ae92..227f2dd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +0.9.3 +===== +- New hook: `mixed-line-ending` + 0.9.2 ===== - Report full python version in `check-ast`. diff --git a/README.md b/README.md index 8efd74b..85ea6dd 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v0.9.2 # Use the ref you want to point at + sha: v0.9.3 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 2563095..db6218d 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='0.9.2', + version='0.9.3', author='Anthony Sottile', author_email='asottile@umich.edu', From c5b7c35d81174fa8e69c50f6561fa9537f1334e5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 8 Sep 2017 08:27:04 -0700 Subject: [PATCH 053/114] Use fixtures for the symlink tests to fix appveyor --- testing/resources/broken_symlink | 1 - testing/resources/working_symlink | 1 - tests/check_symlinks_test.py | 23 ++++++++++++++--------- 3 files changed, 14 insertions(+), 11 deletions(-) delete mode 120000 testing/resources/broken_symlink delete mode 120000 testing/resources/working_symlink diff --git a/testing/resources/broken_symlink b/testing/resources/broken_symlink deleted file mode 120000 index ee1f6cb..0000000 --- a/testing/resources/broken_symlink +++ /dev/null @@ -1 +0,0 @@ -does_not_exist \ No newline at end of file diff --git a/testing/resources/working_symlink b/testing/resources/working_symlink deleted file mode 120000 index 20b5061..0000000 --- a/testing/resources/working_symlink +++ /dev/null @@ -1 +0,0 @@ -does_exist \ No newline at end of file diff --git a/tests/check_symlinks_test.py b/tests/check_symlinks_test.py index 19bb5b4..0414df5 100644 --- a/tests/check_symlinks_test.py +++ b/tests/check_symlinks_test.py @@ -3,16 +3,21 @@ import os import pytest from pre_commit_hooks.check_symlinks import check_symlinks -from testing.util import get_resource_path -@pytest.mark.xfail(os.name == 'nt', reason='No symlink support on windows') +xfail_symlink = pytest.mark.xfail(os.name == 'nt', reason='No symlink support') + + +@xfail_symlink @pytest.mark.parametrize( - ('filename', 'expected_retval'), ( - ('broken_symlink', 1), - ('working_symlink', 0), - ), + ('dest', 'expected'), (('exists', 0), ('does-not-exist', 1)), ) -def test_check_symlinks(filename, expected_retval): - ret = check_symlinks([get_resource_path(filename)]) - assert ret == expected_retval +def test_check_symlinks(tmpdir, dest, expected): # pragma: no cover (symlinks) + tmpdir.join('exists').ensure() + symlink = tmpdir.join('symlink') + symlink.mksymlinkto(tmpdir.join(dest)) + assert check_symlinks((symlink.strpath,)) == expected + + +def test_check_symlinks_normal_file(tmpdir): + assert check_symlinks((tmpdir.join('f').ensure().strpath,)) == 0 From 54ec15fa8985a1eb8782643d84afba7e1506536f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 19 Sep 2017 10:14:14 -0700 Subject: [PATCH 054/114] Fix mixed-line-endings entrypoint --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index db6218d..34c510f 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ setup( 'file-contents-sorter = pre_commit_hooks.file_contents_sorter:main', 'fix-encoding-pragma = pre_commit_hooks.fix_encoding_pragma:main', 'forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main', - 'mixed-line-ending = pre_commit_hooks.mixed_line_ending:mixed_line_ending', + 'mixed-line-ending = pre_commit_hooks.mixed_line_ending:main', 'name-tests-test = pre_commit_hooks.tests_should_end_in_test:validate_files', 'no-commit-to-branch = pre_commit_hooks.no_commit_to_branch:main', 'pretty-format-json = pre_commit_hooks.pretty_format_json:pretty_format_json', From efdceb4e40cda10780f4646ec944f55b5786190d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 19 Sep 2017 10:26:18 -0700 Subject: [PATCH 055/114] v0.9.4 --- .pre-commit-config.yaml | 2 +- CHANGELOG | 4 ++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07ab748..2d0a933 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v0.9.3 + sha: v0.9.4 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG b/CHANGELOG index 227f2dd..ef06046 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +0.9.4 +===== +- Fix entry point for `mixed-line-ending` + 0.9.3 ===== - New hook: `mixed-line-ending` diff --git a/README.md b/README.md index 85ea6dd..16fa8e7 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v0.9.3 # Use the ref you want to point at + sha: v0.9.4 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 34c510f..1709931 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='0.9.3', + version='0.9.4', author='Anthony Sottile', author_email='asottile@umich.edu', From 76047f6eefd4dd2794a24904b9d154fe85fec412 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 27 Sep 2017 07:47:24 -0700 Subject: [PATCH 056/114] Fix mixed-line-endings --fix=... when whole file is a different ending --- pre_commit_hooks/mixed_line_ending.py | 3 ++- tests/mixed_line_ending_test.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pre_commit_hooks/mixed_line_ending.py b/pre_commit_hooks/mixed_line_ending.py index 301c654..a163726 100644 --- a/pre_commit_hooks/mixed_line_ending.py +++ b/pre_commit_hooks/mixed_line_ending.py @@ -55,7 +55,8 @@ def fix_filename(filename, fix): else: target_ending = FIX_TO_LINE_ENDING[fix] # find if there are lines with *other* endings - del counts[target_ending] + # It's possible there's no line endings of the target type + counts.pop(target_ending, None) other_endings = bool(sum(counts.values())) if other_endings: _fix(filename, contents, target_ending) diff --git a/tests/mixed_line_ending_test.py b/tests/mixed_line_ending_test.py index 808295b..23837cd 100644 --- a/tests/mixed_line_ending_test.py +++ b/tests/mixed_line_ending_test.py @@ -101,3 +101,13 @@ def test_fix_crlf(tmpdir): assert ret == 1 assert path.read_binary() == b'foo\r\nbar\r\nbaz\r\n' + + +def test_fix_lf_all_crlf(tmpdir): + """Regression test for #239""" + path = tmpdir.join('input.txt') + path.write_binary(b'foo\r\nbar\r\n') + ret = main(('--fix=lf', path.strpath)) + + assert ret == 1 + assert path.read_binary() == b'foo\nbar\n' From dec98f7bc860eb34889eab98bcc8eebd2a379b62 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 27 Sep 2017 07:55:32 -0700 Subject: [PATCH 057/114] v0.9.5 --- .pre-commit-config.yaml | 2 +- CHANGELOG | 4 ++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d0a933..6da9f6a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v0.9.4 + sha: v0.9.5 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG b/CHANGELOG index ef06046..f3bef02 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +0.9.5 +===== +- Fix mixed-line-endings `--fix=...` when whole file is a different ending + 0.9.4 ===== - Fix entry point for `mixed-line-ending` diff --git a/README.md b/README.md index 16fa8e7..147b6ba 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v0.9.4 # Use the ref you want to point at + sha: v0.9.5 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 1709931..2c26c9b 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='0.9.4', + version='0.9.5', author='Anthony Sottile', author_email='asottile@umich.edu', From 9db0a745f662494cdb027b990d2a949c37044964 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 6 Oct 2017 23:32:11 -0700 Subject: [PATCH 058/114] Add hook for ensuring vcs permalinks --- .pre-commit-hooks.yaml | 9 +++++ README.md | 1 + hooks.yaml | 6 ++++ pre_commit_hooks/check_vcs_permalinks.py | 43 ++++++++++++++++++++++++ setup.py | 1 + tests/check_vcs_permalinks_test.py | 36 ++++++++++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 pre_commit_hooks/check_vcs_permalinks.py create mode 100644 tests/check_vcs_permalinks_test.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 454f258..f5b73d3 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -96,6 +96,15 @@ # for backward compatibility files: '' minimum_pre_commit_version: 0.15.0 +- id: check-vcs-permalinks + name: Check vcs permalinks + description: Ensures that links to vcs websites are permalinks. + entry: check-vcs-permalinks + language: python + types: [text] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-xml name: Check Xml description: This hook checks xml files for parseable syntax. diff --git a/README.md b/README.md index 147b6ba..501401e 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Add this to your `.pre-commit-config.yaml` - `check-json` - Attempts to load all json files to verify syntax. - `check-merge-conflict` - Check for files that contain merge conflict strings. - `check-symlinks` - Checks for symlinks which do not point to anything. +- `check-vcs-permalinks` - Ensures that links to vcs websites are permalinks. - `check-xml` - Attempts to load all xml files to verify syntax. - `check-yaml` - Attempts to load all yaml files to verify syntax. - `debug-statements` - Check for pdb / ipdb / pudb statements in code. diff --git a/hooks.yaml b/hooks.yaml index 59cc320..6d66935 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -64,6 +64,12 @@ entry: upgrade-your-pre-commit-version files: '' minimum_pre_commit_version: 0.15.0 +- id: check-vcs-permalinks + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-xml language: system name: upgrade-your-pre-commit-version diff --git a/pre_commit_hooks/check_vcs_permalinks.py b/pre_commit_hooks/check_vcs_permalinks.py new file mode 100644 index 0000000..130abf6 --- /dev/null +++ b/pre_commit_hooks/check_vcs_permalinks.py @@ -0,0 +1,43 @@ +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import argparse +import re +import sys + + +GITHUB_NON_PERMALINK = re.compile( + b'https://github.com/[^/]+/[^/]+/blob/master/[^# ]+#L\d+', +) + + +def _check_filename(filename): + retv = 0 + with open(filename, 'rb') as f: + for i, line in enumerate(f, 1): + if GITHUB_NON_PERMALINK.search(line): + sys.stdout.write('{}:{}:'.format(filename, i)) + getattr(sys.stdout, 'buffer', sys.stdout).write(line) + retv = 1 + return retv + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument('filenames', nargs='*') + args = parser.parse_args(argv) + + retv = 0 + for filename in args.filenames: + retv |= _check_filename(filename) + + if retv: + print() + print('Non-permanent github link detected.') + print('On any page on github press [y] to load a permalink.') + return retv + + +if __name__ == '__main__': + exit(main()) diff --git a/setup.py b/setup.py index 2c26c9b..73cace5 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ setup( 'check-json = pre_commit_hooks.check_json:check_json', 'check-merge-conflict = pre_commit_hooks.check_merge_conflict:detect_merge_conflict', 'check-symlinks = pre_commit_hooks.check_symlinks:check_symlinks', + 'check-vcs-permalinks = pre_commit_hooks.check_vcs_permalinks:main', 'check-xml = pre_commit_hooks.check_xml:check_xml', 'check-yaml = pre_commit_hooks.check_yaml:check_yaml', 'debug-statement-hook = pre_commit_hooks.debug_statement_hook:debug_statement_hook', diff --git a/tests/check_vcs_permalinks_test.py b/tests/check_vcs_permalinks_test.py new file mode 100644 index 0000000..31fd608 --- /dev/null +++ b/tests/check_vcs_permalinks_test.py @@ -0,0 +1,36 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +from pre_commit_hooks.check_vcs_permalinks import main + + +def test_trivial(tmpdir): + f = tmpdir.join('f.txt').ensure() + assert not main((f.strpath,)) + + +def test_passing(tmpdir): + f = tmpdir.join('f.txt') + f.write_binary( + # permalinks are ok + b'https://github.com/asottile/test/blob/649e6/foo%20bar#L1\n' + # links to files but not line numbers are ok + b'https://github.com/asottile/test/blob/master/foo%20bar\n', + ) + assert not main((f.strpath,)) + + +def test_failing(tmpdir, capsys): + with tmpdir.as_cwd(): + tmpdir.join('f.txt').write_binary( + b'https://github.com/asottile/test/blob/master/foo#L1\n', + ) + + assert main(('f.txt',)) + out, _ = capsys.readouterr() + assert out == ( + 'f.txt:1:https://github.com/asottile/test/blob/master/foo#L1\n' + '\n' + 'Non-permanent github link detected.\n' + 'On any page on github press [y] to load a permalink.\n' + ) From 9958d1e21938a209eef7c59defa0c3a555ca58e4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 9 Oct 2017 10:39:58 -0700 Subject: [PATCH 059/114] Fix trailing-whitespace on macos for non-utf8 files --- pre_commit_hooks/trailing_whitespace_fixer.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/pre_commit_hooks/trailing_whitespace_fixer.py b/pre_commit_hooks/trailing_whitespace_fixer.py index d44750d..26f7b51 100644 --- a/pre_commit_hooks/trailing_whitespace_fixer.py +++ b/pre_commit_hooks/trailing_whitespace_fixer.py @@ -4,8 +4,6 @@ import argparse import os import sys -from pre_commit_hooks.util import cmd_output - def _fix_file(filename, is_markdown): with open(filename, mode='rb') as file_processed: @@ -50,10 +48,6 @@ def fix_trailing_whitespace(argv=None): parser.add_argument('filenames', nargs='*', help='Filenames to fix') args = parser.parse_args(argv) - bad_whitespace_files = cmd_output( - 'grep', '-l', '[[:space:]]$', *args.filenames, retcode=None - ).strip().splitlines() - md_args = args.markdown_linebreak_ext if '' in md_args: parser.error('--markdown-linebreak-ext requires a non-empty argument') @@ -73,11 +67,11 @@ def fix_trailing_whitespace(argv=None): ) return_code = 0 - for bad_whitespace_file in bad_whitespace_files: - _, extension = os.path.splitext(bad_whitespace_file.lower()) + for filename in args.filenames: + _, extension = os.path.splitext(filename.lower()) md = all_markdown or extension in md_exts - if _fix_file(bad_whitespace_file, md): - print('Fixing {}'.format(bad_whitespace_file)) + if _fix_file(filename, md): + print('Fixing {}'.format(filename)) return_code = 1 return return_code From 86691eda4761e3ab26938d73de8a996a0293c54b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 9 Oct 2017 10:59:17 -0700 Subject: [PATCH 060/114] Fix requirements-txt-fixer for comments at end of file --- pre_commit_hooks/requirements_txt_fixer.py | 7 +++++++ tests/requirements_txt_fixer_test.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py index ffabf2a..1ee6fac 100644 --- a/pre_commit_hooks/requirements_txt_fixer.py +++ b/pre_commit_hooks/requirements_txt_fixer.py @@ -63,9 +63,16 @@ def fix_requirements(f): else: requirement.value = line + # if a file ends in a comment, preserve it at the end + if requirements[-1].value is None: + rest = requirements.pop().comments + else: + rest = [] + for requirement in sorted(requirements): after.extend(requirement.comments) after.append(requirement.value) + after.extend(rest) after_string = b''.join(after) diff --git a/tests/requirements_txt_fixer_test.py b/tests/requirements_txt_fixer_test.py index dcf7a76..87e7b0c 100644 --- a/tests/requirements_txt_fixer_test.py +++ b/tests/requirements_txt_fixer_test.py @@ -11,6 +11,8 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement ( (b'', PASS, b''), (b'\n', PASS, b'\n'), + (b'# intentionally empty\n', PASS, b'# intentionally empty\n'), + (b'foo\n# comment at end\n', PASS, b'foo\n# comment at end\n'), (b'foo\nbar\n', FAIL, b'bar\nfoo\n'), (b'bar\nfoo\n', PASS, b'bar\nfoo\n'), (b'#comment1\nfoo\n#comment2\nbar\n', FAIL, b'#comment2\nbar\n#comment1\nfoo\n'), From c326dc2fce66532afd4c98165ec3971dfe9f7c66 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 9 Oct 2017 13:17:25 -0700 Subject: [PATCH 061/114] v1.0.0 --- .pre-commit-config.yaml | 2 +- CHANGELOG | 11 +++++++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6da9f6a..63a605a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v0.9.5 + sha: v1.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG b/CHANGELOG index f3bef02..95d0d63 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +1.0.0 +===== +### Features: +- New hook: `check-vcs-permalinks` for ensuring permalinked github urls. + - #241 PR by @asottile. +### Fixes: +- Fix `trailing-whitespace` for non-utf8 files on macos + - #242 PR by @asottile. +- Fix `requirements-txt-fixer` for files ending in comments + - #243 PR by @asottile. + 0.9.5 ===== - Fix mixed-line-endings `--fix=...` when whole file is a different ending diff --git a/README.md b/README.md index 501401e..c4f9dc1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v0.9.5 # Use the ref you want to point at + sha: v1.0.0 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 73cace5..0a8114d 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='0.9.5', + version='1.0.0', author='Anthony Sottile', author_email='asottile@umich.edu', From e09278e806c57f7f45a184f8e0e1e22d33a1ba84 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 9 Oct 2017 13:20:16 -0700 Subject: [PATCH 062/114] CHANGELOG is a markdown file --- CHANGELOG => CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) rename CHANGELOG => CHANGELOG.md (99%) diff --git a/CHANGELOG b/CHANGELOG.md similarity index 99% rename from CHANGELOG rename to CHANGELOG.md index 95d0d63..1256926 100644 --- a/CHANGELOG +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features: - New hook: `check-vcs-permalinks` for ensuring permalinked github urls. - #241 PR by @asottile. + ### Fixes: - Fix `trailing-whitespace` for non-utf8 files on macos - #242 PR by @asottile. From e87b81afd90488b44364f7bb7e345d1e993271af Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 12 Oct 2017 15:47:20 -0700 Subject: [PATCH 063/114] Add an --allow-multiple-documents option to check-yaml --- README.md | 2 ++ pre_commit_hooks/check_yaml.py | 11 ++++++++++- tests/check_yaml_test.py | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c4f9dc1..79d28fb 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ Add this to your `.pre-commit-config.yaml` - `check-vcs-permalinks` - Ensures that links to vcs websites are permalinks. - `check-xml` - Attempts to load all xml files to verify syntax. - `check-yaml` - Attempts to load all yaml files to verify syntax. + - `--allow-multiple-documents` - allow yaml files which use the + [multi-document syntax](http://www.yaml.org/spec/1.2/spec.html#YAML) - `debug-statements` - Check for pdb / ipdb / pudb statements in code. - `detect-aws-credentials` - Checks for the existence of AWS secrets that you have set up with the AWS CLI. diff --git a/pre_commit_hooks/check_yaml.py b/pre_commit_hooks/check_yaml.py index cc9a614..e9bb8f0 100644 --- a/pre_commit_hooks/check_yaml.py +++ b/pre_commit_hooks/check_yaml.py @@ -11,15 +11,24 @@ except ImportError: # pragma: no cover (no libyaml-dev / pypy) Loader = yaml.SafeLoader +def _load_all(*args, **kwargs): + # need to exhaust the generator + return tuple(yaml.load_all(*args, **kwargs)) + + def check_yaml(argv=None): parser = argparse.ArgumentParser() + parser.add_argument( + '-m', '--allow-multiple-documents', dest='yaml_load_fn', + action='store_const', const=_load_all, default=yaml.load, + ) parser.add_argument('filenames', nargs='*', help='Yaml filenames to check.') args = parser.parse_args(argv) retval = 0 for filename in args.filenames: try: - yaml.load(open(filename), Loader=Loader) + args.yaml_load_fn(open(filename), Loader=Loader) except yaml.YAMLError as exc: print(exc) retval = 1 diff --git a/tests/check_yaml_test.py b/tests/check_yaml_test.py index 73d6593..de3b383 100644 --- a/tests/check_yaml_test.py +++ b/tests/check_yaml_test.py @@ -1,3 +1,6 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + import pytest from pre_commit_hooks.check_yaml import check_yaml @@ -13,3 +16,20 @@ from testing.util import get_resource_path def test_check_yaml(filename, expected_retval): ret = check_yaml([get_resource_path(filename)]) assert ret == expected_retval + + +def test_check_yaml_allow_multiple_documents(tmpdir): + f = tmpdir.join('test.yaml') + f.write('---\nfoo\n---\nbar\n') + + # should failw without the setting + assert check_yaml((f.strpath,)) + + # should pass when we allow multiple documents + assert not check_yaml(('--allow-multiple-documents', f.strpath)) + + +def test_fails_even_with_allow_multiple_documents(tmpdir): + f = tmpdir.join('test.yaml') + f.write('[') + assert check_yaml(('--allow-multiple-documents', f.strpath)) From 8a98c5e59001c2b3b3848291f19eb043ff02e8dc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 12 Oct 2017 16:00:22 -0700 Subject: [PATCH 064/114] v1.1.0 --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 9 +++++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 63a605a..1efefdb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v1.0.0 + sha: v1.1.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG.md b/CHANGELOG.md index 1256926..3d1b762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +1.1.0 +===== +### Features: +- `check-yaml` gains a `--allow-multiple-documents` (`-m`) argument to allow + linting of files using the + [multi document syntax](http://www.yaml.org/spec/1.2/spec.html#YAML) + - pre-commit/pre-commit#635 issue by @geekobi. + - #244 PR by @asottile. + 1.0.0 ===== ### Features: diff --git a/README.md b/README.md index 79d28fb..035931b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v1.0.0 # Use the ref you want to point at + sha: v1.1.0 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 0a8114d..fc94335 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='1.0.0', + version='1.1.0', author='Anthony Sottile', author_email='asottile@umich.edu', From 9567db66e5a1d16b4c8055832710fbabee8431f8 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 18 Oct 2017 14:46:38 -0700 Subject: [PATCH 065/114] Add a flush --- pre_commit_hooks/check_vcs_permalinks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pre_commit_hooks/check_vcs_permalinks.py b/pre_commit_hooks/check_vcs_permalinks.py index 130abf6..d67d5f5 100644 --- a/pre_commit_hooks/check_vcs_permalinks.py +++ b/pre_commit_hooks/check_vcs_permalinks.py @@ -18,6 +18,7 @@ def _check_filename(filename): for i, line in enumerate(f, 1): if GITHUB_NON_PERMALINK.search(line): sys.stdout.write('{}:{}:'.format(filename, i)) + sys.stdout.flush() getattr(sys.stdout, 'buffer', sys.stdout).write(line) retv = 1 return retv From f1e943e6bf1e677edaa5c36e77c13f8043e0ce0d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 19 Oct 2017 10:34:23 -0700 Subject: [PATCH 066/114] v1.1.1 --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 6 ++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1efefdb..bf9d525 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v1.1.0 + sha: v1.1.1 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d1b762..337739c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +1.1.1 +===== +### Fixes: +- Fix output interleaving in `check-vcs-permalinks` under python3. + - #245 PR by @asottile. + 1.1.0 ===== ### Features: diff --git a/README.md b/README.md index 035931b..128c0c0 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v1.1.0 # Use the ref you want to point at + sha: v1.1.1 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index fc94335..d5296de 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='1.1.0', + version='1.1.1', author='Anthony Sottile', author_email='asottile@umich.edu', From e9a0f84a690a09d02d5dc53d4ba3e5ac2a779dfd Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 21 Oct 2017 08:59:57 -0700 Subject: [PATCH 067/114] git ls-files -z -- .coveragerc | xargs -0 sed -i '/ \*\/tmp\*/d' Committed via https://github.com/asottile/all-repos --- .coveragerc | 1 - 1 file changed, 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 732613b..0a55c5f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,7 +5,6 @@ source = omit = .tox/* /usr/* - */tmp* setup.py get-git-lfs.py From 35996b7a256ac7445edc2492457e45b17c896c34 Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Sun, 26 Nov 2017 00:17:47 +0000 Subject: [PATCH 068/114] Add check to enforce literal syntax for Python builtin types This check requires authors to initialize empty or zero builtin types using the literal syntax (e.g., `{}` instead of `dict()`). Authors may ignore this requirement for certain builtins using the `--ignore` option. Authors may also forbid calling `dict()` with keyword arguments (`dict(a=1, b=2)`) using the `--no-allow-dict-kwargs` flag. --- .pre-commit-hooks.yaml | 9 ++ README.md | 4 + hooks.yaml | 6 ++ pre_commit_hooks/check_builtin_literals.py | 90 +++++++++++++++++ setup.py | 1 + testing/resources/builtin_constructors.py | 7 ++ testing/resources/builtin_literals.py | 7 ++ tests/check_builtin_literals_test.py | 107 +++++++++++++++++++++ 8 files changed, 231 insertions(+) create mode 100644 pre_commit_hooks/check_builtin_literals.py create mode 100644 testing/resources/builtin_constructors.py create mode 100644 testing/resources/builtin_literals.py create mode 100644 tests/check_builtin_literals_test.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index f5b73d3..f1a901e 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -34,6 +34,15 @@ # for backward compatibility files: '' minimum_pre_commit_version: 0.15.0 +- id: check-builtin-literals + name: Check builtin type constructor use + description: Require literal syntax when initializing empty or zero Python builtin types. + entry: check-builtin-literals + language: python + types: [python] + # for backward compatibility + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-case-conflict name: Check for case conflicts description: Check for files that would conflict in case-insensitive filesystems diff --git a/README.md b/README.md index 128c0c0..ff596ac 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ Add this to your `.pre-commit-config.yaml` - `check-added-large-files` - Prevent giant files from being committed. - Specify what is "too large" with `args: ['--maxkb=123']` (default=500kB). - `check-ast` - Simply check whether files parse as valid python. +- `check-builtin-literals` - Require literal syntax when initializing empty or zero Python builtin types. + - Allows calling constructors with positional arguments (e.g., `list('abc')`). + - Ignore this requirement for specific builtin types with `--ignore=type1,type2,…`. + - Forbid `dict` keyword syntax with `--no-allow-dict-kwargs`. - `check-byte-order-marker` - Forbid files which have a UTF-8 byte-order marker - `check-case-conflict` - Check for files with names that would conflict on a case-insensitive filesystem like MacOS HFS+ or Windows FAT. diff --git a/hooks.yaml b/hooks.yaml index 6d66935..4552fa7 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -16,6 +16,12 @@ entry: upgrade-your-pre-commit-version files: '' minimum_pre_commit_version: 0.15.0 +- id: check-builtin-literals + language: system + name: upgrade-your-pre-commit-version + entry: upgrade-your-pre-commit-version + files: '' + minimum_pre_commit_version: 0.15.0 - id: check-byte-order-marker language: system name: upgrade-your-pre-commit-version diff --git a/pre_commit_hooks/check_builtin_literals.py b/pre_commit_hooks/check_builtin_literals.py new file mode 100644 index 0000000..1213288 --- /dev/null +++ b/pre_commit_hooks/check_builtin_literals.py @@ -0,0 +1,90 @@ +from __future__ import unicode_literals + +import argparse +import ast +import collections +import sys + + +BUILTIN_TYPES = { + 'complex': '0j', + 'dict': '{}', + 'float': '0.0', + 'int': '0', + 'list': '[]', + 'str': "''", + 'tuple': '()', +} + + +BuiltinTypeCall = collections.namedtuple('BuiltinTypeCall', ['name', 'line', 'column']) + + +class BuiltinTypeVisitor(ast.NodeVisitor): + def __init__(self, ignore=None, allow_dict_kwargs=True): + self.builtin_type_calls = [] + self.ignore = set(ignore) if ignore else set() + self.allow_dict_kwargs = allow_dict_kwargs + + def _check_dict_call(self, node): + return self.allow_dict_kwargs and (getattr(node, 'kwargs', None) or getattr(node, 'keywords', None)) + + def visit_Call(self, node): + if node.func.id not in set(BUILTIN_TYPES).difference(self.ignore): + return + if node.func.id == 'dict' and self._check_dict_call(node): + return + elif node.args: + return + self.builtin_type_calls.append( + BuiltinTypeCall(node.func.id, node.lineno, node.col_offset), + ) + + +def check_file_for_builtin_type_constructors(filename, ignore=None, allow_dict_kwargs=True): + tree = ast.parse(open(filename, 'rb').read(), filename=filename) + visitor = BuiltinTypeVisitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs) + visitor.visit(tree) + return visitor.builtin_type_calls + + +def parse_args(argv): + def parse_ignore(value): + return set(value.split(',')) + + parser = argparse.ArgumentParser() + parser.add_argument('filenames', nargs='*') + parser.add_argument('--ignore', type=parse_ignore, default=set()) + + allow_dict_kwargs = parser.add_mutually_exclusive_group(required=False) + allow_dict_kwargs.add_argument('--allow-dict-kwargs', action='store_true') + allow_dict_kwargs.add_argument('--no-allow-dict-kwargs', dest='allow_dict_kwargs', action='store_false') + allow_dict_kwargs.set_defaults(allow_dict_kwargs=True) + + return parser.parse_args(argv) + + +def main(argv=None): + args = parse_args(argv) + rc = 0 + for filename in args.filenames: + calls = check_file_for_builtin_type_constructors( + filename, + ignore=args.ignore, + allow_dict_kwargs=args.allow_dict_kwargs, + ) + if calls: + rc = rc or 1 + for call in calls: + print( + '{filename}:{call.line}:{call.column} - Replace {call.name}() with {replacement}'.format( + filename=filename, + call=call, + replacement=BUILTIN_TYPES[call.name], + ), + ) + return rc + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/setup.py b/setup.py index d5296de..ce8de14 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ setup( 'autopep8-wrapper = pre_commit_hooks.autopep8_wrapper:main', 'check-added-large-files = pre_commit_hooks.check_added_large_files:main', 'check-ast = pre_commit_hooks.check_ast:check_ast', + 'check-builtin-literals = pre_commit_hooks.check_builtin_literals:main', 'check-byte-order-marker = pre_commit_hooks.check_byte_order_marker:main', 'check-case-conflict = pre_commit_hooks.check_case_conflict:main', 'check-docstring-first = pre_commit_hooks.check_docstring_first:main', diff --git a/testing/resources/builtin_constructors.py b/testing/resources/builtin_constructors.py new file mode 100644 index 0000000..3fab056 --- /dev/null +++ b/testing/resources/builtin_constructors.py @@ -0,0 +1,7 @@ +c1 = complex() +d1 = dict() +f1 = float() +i1 = int() +l1 = list() +s1 = str() +t1 = tuple() diff --git a/testing/resources/builtin_literals.py b/testing/resources/builtin_literals.py new file mode 100644 index 0000000..8513b70 --- /dev/null +++ b/testing/resources/builtin_literals.py @@ -0,0 +1,7 @@ +c1 = 0j +d1 = {} +f1 = 0.0 +i1 = 0 +l1 = [] +s1 = '' +t1 = () diff --git a/tests/check_builtin_literals_test.py b/tests/check_builtin_literals_test.py new file mode 100644 index 0000000..a38e522 --- /dev/null +++ b/tests/check_builtin_literals_test.py @@ -0,0 +1,107 @@ +import ast + +import pytest + +from pre_commit_hooks.check_builtin_literals import BuiltinTypeCall +from pre_commit_hooks.check_builtin_literals import BuiltinTypeVisitor +from pre_commit_hooks.check_builtin_literals import main +from testing.util import get_resource_path + + +@pytest.fixture +def visitor(): + return BuiltinTypeVisitor() + + +@pytest.mark.parametrize( + ('expression', 'calls'), + [ + # complex + ("0j", []), + ("complex()", [BuiltinTypeCall('complex', 1, 0)]), + ("complex(0, 0)", []), + ("complex('0+0j')", []), + # float + ("0.0", []), + ("float()", [BuiltinTypeCall('float', 1, 0)]), + ("float('0.0')", []), + # int + ("0", []), + ("int()", [BuiltinTypeCall('int', 1, 0)]), + ("int('0')", []), + # list + ("[]", []), + ("list()", [BuiltinTypeCall('list', 1, 0)]), + ("list('abc')", []), + ("list([c for c in 'abc'])", []), + ("list(c for c in 'abc')", []), + # str + ("''", []), + ("str()", [BuiltinTypeCall('str', 1, 0)]), + ("str('0')", []), + ("[]", []), + # tuple + ("()", []), + ("tuple()", [BuiltinTypeCall('tuple', 1, 0)]), + ("tuple('abc')", []), + ("tuple([c for c in 'abc'])", []), + ("tuple(c for c in 'abc')", []), + ], +) +def test_non_dict_exprs(visitor, expression, calls): + visitor.visit(ast.parse(expression)) + assert visitor.builtin_type_calls == calls + + +@pytest.mark.parametrize( + ('expression', 'calls'), + [ + ("{}", []), + ("dict()", [BuiltinTypeCall('dict', 1, 0)]), + ("dict(a=1, b=2, c=3)", []), + ("dict(**{'a': 1, 'b': 2, 'c': 3})", []), + ("dict([(k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)]])", []), + ("dict((k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)])", []), + ], +) +def test_dict_allow_kwargs_exprs(visitor, expression, calls): + visitor.visit(ast.parse(expression)) + assert visitor.builtin_type_calls == calls + + +@pytest.mark.parametrize( + ('expression', 'calls'), + [ + ("dict()", [BuiltinTypeCall('dict', 1, 0)]), + ("dict(a=1, b=2, c=3)", [BuiltinTypeCall('dict', 1, 0)]), + ("dict(**{'a': 1, 'b': 2, 'c': 3})", [BuiltinTypeCall('dict', 1, 0)]), + ], +) +def test_dict_no_allow_kwargs_exprs(expression, calls): + visitor = BuiltinTypeVisitor(allow_dict_kwargs=False) + visitor.visit(ast.parse(expression)) + assert visitor.builtin_type_calls == calls + + +def test_ignore_constructors(): + visitor = BuiltinTypeVisitor(ignore=('complex', 'dict', 'float', 'int', 'list', 'str', 'tuple')) + visitor.visit(ast.parse(open(get_resource_path('builtin_constructors.py'), 'rb').read(), 'builtin_constructors.py')) + assert visitor.builtin_type_calls == [] + + +def test_failing_file(): + rc = main([get_resource_path('builtin_constructors.py')]) + assert rc == 1 + + +def test_passing_file(): + rc = main([get_resource_path('builtin_literals.py')]) + assert rc == 0 + + +def test_failing_file_ignore_all(): + rc = main([ + '--ignore=complex,dict,float,int,list,str,tuple', + get_resource_path('builtin_constructors.py'), + ]) + assert rc == 0 From 775867626df1a5a2b36dd73c7e27f3a679b4a427 Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Thu, 30 Nov 2017 18:27:16 +0000 Subject: [PATCH 069/114] check-builtin-literals: Ignore function attribute calls --- README.md | 1 + pre_commit_hooks/check_builtin_literals.py | 5 +++++ testing/resources/builtin_constructors.py | 11 +++++++++++ tests/check_builtin_literals_test.py | 9 ++++++++- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ff596ac..d717137 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Add this to your `.pre-commit-config.yaml` - `check-ast` - Simply check whether files parse as valid python. - `check-builtin-literals` - Require literal syntax when initializing empty or zero Python builtin types. - Allows calling constructors with positional arguments (e.g., `list('abc')`). + - Allows calling constructors from the `builtins` (`__builtin__`) namespace (`builtins.list()`). - Ignore this requirement for specific builtin types with `--ignore=type1,type2,…`. - Forbid `dict` keyword syntax with `--no-allow-dict-kwargs`. - `check-byte-order-marker` - Forbid files which have a UTF-8 byte-order marker diff --git a/pre_commit_hooks/check_builtin_literals.py b/pre_commit_hooks/check_builtin_literals.py index 1213288..c4ac969 100644 --- a/pre_commit_hooks/check_builtin_literals.py +++ b/pre_commit_hooks/check_builtin_literals.py @@ -30,6 +30,11 @@ class BuiltinTypeVisitor(ast.NodeVisitor): return self.allow_dict_kwargs and (getattr(node, 'kwargs', None) or getattr(node, 'keywords', None)) def visit_Call(self, node): + if isinstance(node.func, ast.Attribute): + # Ignore functions that are object attributes (`foo.bar()`). + # Assume that if the user calls `builtins.list()`, they know what + # they're doing. + return if node.func.id not in set(BUILTIN_TYPES).difference(self.ignore): return if node.func.id == 'dict' and self._check_dict_call(node): diff --git a/testing/resources/builtin_constructors.py b/testing/resources/builtin_constructors.py index 3fab056..720bd2b 100644 --- a/testing/resources/builtin_constructors.py +++ b/testing/resources/builtin_constructors.py @@ -1,3 +1,6 @@ +# flake8 checks imports; `builtins` does not exist in Python 2. +# flake8: noqa + c1 = complex() d1 = dict() f1 = float() @@ -5,3 +8,11 @@ i1 = int() l1 = list() s1 = str() t1 = tuple() + +c2 = builtins.complex() +d2 = builtins.dict() +f2 = builtins.float() +i2 = builtins.int() +l2 = builtins.list() +s2 = builtins.str() +t2 = builtins.tuple() diff --git a/tests/check_builtin_literals_test.py b/tests/check_builtin_literals_test.py index a38e522..13f896a 100644 --- a/tests/check_builtin_literals_test.py +++ b/tests/check_builtin_literals_test.py @@ -21,31 +21,36 @@ def visitor(): ("complex()", [BuiltinTypeCall('complex', 1, 0)]), ("complex(0, 0)", []), ("complex('0+0j')", []), + ('builtins.complex()', []), # float ("0.0", []), ("float()", [BuiltinTypeCall('float', 1, 0)]), ("float('0.0')", []), + ('builtins.float()', []), # int ("0", []), ("int()", [BuiltinTypeCall('int', 1, 0)]), ("int('0')", []), + ('builtins.int()', []), # list ("[]", []), ("list()", [BuiltinTypeCall('list', 1, 0)]), ("list('abc')", []), ("list([c for c in 'abc'])", []), ("list(c for c in 'abc')", []), + ('builtins.list()', []), # str ("''", []), ("str()", [BuiltinTypeCall('str', 1, 0)]), ("str('0')", []), - ("[]", []), + ('builtins.str()', []), # tuple ("()", []), ("tuple()", [BuiltinTypeCall('tuple', 1, 0)]), ("tuple('abc')", []), ("tuple([c for c in 'abc'])", []), ("tuple(c for c in 'abc')", []), + ('builtins.tuple()', []), ], ) def test_non_dict_exprs(visitor, expression, calls): @@ -62,6 +67,7 @@ def test_non_dict_exprs(visitor, expression, calls): ("dict(**{'a': 1, 'b': 2, 'c': 3})", []), ("dict([(k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)]])", []), ("dict((k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)])", []), + ('builtins.dict()', []), ], ) def test_dict_allow_kwargs_exprs(visitor, expression, calls): @@ -75,6 +81,7 @@ def test_dict_allow_kwargs_exprs(visitor, expression, calls): ("dict()", [BuiltinTypeCall('dict', 1, 0)]), ("dict(a=1, b=2, c=3)", [BuiltinTypeCall('dict', 1, 0)]), ("dict(**{'a': 1, 'b': 2, 'c': 3})", [BuiltinTypeCall('dict', 1, 0)]), + ('builtins.dict()', []), ], ) def test_dict_no_allow_kwargs_exprs(expression, calls): From d889840b883231141e020a3f5bf8785f5bfa8d70 Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Fri, 1 Dec 2017 03:04:46 +0000 Subject: [PATCH 070/114] check-builtin-literals: Appease flake8 with six --- testing/resources/builtin_constructors.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testing/resources/builtin_constructors.py b/testing/resources/builtin_constructors.py index 720bd2b..174a9e8 100644 --- a/testing/resources/builtin_constructors.py +++ b/testing/resources/builtin_constructors.py @@ -1,5 +1,4 @@ -# flake8 checks imports; `builtins` does not exist in Python 2. -# flake8: noqa +from six.moves import builtins c1 = complex() d1 = dict() From 1f262dab150c21f7361ed5613effcd550164ac04 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 4 Dec 2017 17:28:46 -0800 Subject: [PATCH 071/114] Document minimum version required for `git-lfs` Resolves #252 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ff596ac..3961204 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Add this to your `.pre-commit-config.yaml` setup.cfg / tox.ini. - `check-added-large-files` - Prevent giant files from being committed. - Specify what is "too large" with `args: ['--maxkb=123']` (default=500kB). + - If `git-lfs` is installed, lfs files will be skipped + (requires `git-lfs>=2.2.1`) - `check-ast` - Simply check whether files parse as valid python. - `check-builtin-literals` - Require literal syntax when initializing empty or zero Python builtin types. - Allows calling constructors with positional arguments (e.g., `list('abc')`). From 00974efa3189b7799d907cad9bebbc9bc3189731 Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Sun, 10 Dec 2017 08:57:34 +0000 Subject: [PATCH 072/114] Remove pretty_format_json simplejson dependency * The simplejson module is only needed for <=py25 so replace with builtin json. * Replace six dependecy for simple Py2 check for convertion to unicode. * Cleanup quotes. --- pre_commit_hooks/pretty_format_json.py | 25 ++++++++++++------------- setup.py | 2 -- tests/pretty_format_json_test.py | 5 ++++- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py index bb7a3d0..9115077 100644 --- a/pre_commit_hooks/pretty_format_json.py +++ b/pre_commit_hooks/pretty_format_json.py @@ -2,11 +2,11 @@ from __future__ import print_function import argparse import io +import json import sys from collections import OrderedDict -import simplejson -import six +from six import text_type def _get_pretty_format(contents, indent, ensure_ascii=True, sort_keys=True, top_keys=[]): @@ -17,18 +17,18 @@ def _get_pretty_format(contents, indent, ensure_ascii=True, sort_keys=True, top_ if sort_keys: after = sorted(after, key=lambda x: x[0]) return OrderedDict(before + after) - return six.text_type(simplejson.dumps( - simplejson.loads( - contents, - object_pairs_hook=pairs_first, - ), + json_pretty = json.dumps( + json.loads(contents, object_pairs_hook=pairs_first), indent=indent, ensure_ascii=ensure_ascii, - )) + "\n" # dumps does not end with a newline + separators=(',', ': '), # Workaround for https://bugs.python.org/issue16333 + ) + # Ensure unicode (Py2) and add the newline that dumps does not end with. + return text_type(json_pretty) + '\n' def _autofix(filename, new_contents): - print("Fixing file {}".format(filename)) + print('Fixing file {}'.format(filename)) with io.open(filename, 'w', encoding='UTF-8') as f: f.write(new_contents) @@ -110,16 +110,15 @@ def pretty_format_json(argv=None): ) if contents != pretty_contents: - print("File {} is not pretty-formatted".format(json_file)) + print('File {} is not pretty-formatted'.format(json_file)) if args.autofix: _autofix(json_file, pretty_contents) status = 1 - - except simplejson.JSONDecodeError: + except ValueError: print( - "Input File {} is not a valid JSON, consider using check-json" + 'Input File {} is not a valid JSON, consider using check-json' .format(json_file), ) return 1 diff --git a/setup.py b/setup.py index ce8de14..ad14930 100644 --- a/setup.py +++ b/setup.py @@ -28,8 +28,6 @@ setup( 'flake8!=2.5.3', 'autopep8>=1.3', 'pyyaml', - 'simplejson', - 'six', ], entry_points={ 'console_scripts': [ diff --git a/tests/pretty_format_json_test.py b/tests/pretty_format_json_test.py index eeef65b..4054b4c 100644 --- a/tests/pretty_format_json_test.py +++ b/tests/pretty_format_json_test.py @@ -1,6 +1,7 @@ import shutil import pytest +from six import PY2 from pre_commit_hooks.pretty_format_json import parse_indent from pre_commit_hooks.pretty_format_json import pretty_format_json @@ -17,6 +18,7 @@ def test_parse_indent(): parse_indent('-2') + @pytest.mark.parametrize( ('filename', 'expected_retval'), ( ('not_pretty_formatted_json.json', 1), @@ -43,6 +45,7 @@ def test_unsorted_pretty_format_json(filename, expected_retval): assert ret == expected_retval +@pytest.mark.skipif(PY2, reason="Requires Python3") @pytest.mark.parametrize( ('filename', 'expected_retval'), ( ('not_pretty_formatted_json.json', 1), @@ -52,7 +55,7 @@ def test_unsorted_pretty_format_json(filename, expected_retval): ('tab_pretty_formatted_json.json', 0), ), ) -def test_tab_pretty_format_json(filename, expected_retval): +def test_tab_pretty_format_json(filename, expected_retval): # pragma: no cover ret = pretty_format_json(['--indent', '\t', get_resource_path(filename)]) assert ret == expected_retval From 5b6ddaf9f7ee96487709cb760a212ef1564b1c83 Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Sun, 10 Dec 2017 09:34:36 +0000 Subject: [PATCH 073/114] Fix pretty_format_json to use int indent The indent parameter for json should be integer and under Python2 is will raise an error if not. So switch from str to int and mention default value in help text. --- pre_commit_hooks/pretty_format_json.py | 29 +++++++++----------------- tests/pretty_format_json_test.py | 16 ++++++-------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py index 9115077..fb1c487 100644 --- a/pre_commit_hooks/pretty_format_json.py +++ b/pre_commit_hooks/pretty_format_json.py @@ -33,24 +33,12 @@ def _autofix(filename, new_contents): f.write(new_contents) -def parse_indent(s): - # type: (str) -> str +def parse_num_to_int(s): + """Convert string numbers to int, leaving strings as is.""" try: - int_indentation_spec = int(s) + return int(s) except ValueError: - if not s.strip(): - return s - else: - raise ValueError( - 'Non-whitespace JSON indentation delimiter supplied. ', - ) - else: - if int_indentation_spec >= 0: - return int_indentation_spec * ' ' - else: - raise ValueError( - 'Negative integer supplied to construct JSON indentation delimiter. ', - ) + return s def parse_topkeys(s): @@ -68,9 +56,12 @@ def pretty_format_json(argv=None): ) parser.add_argument( '--indent', - type=parse_indent, - default=' ', - help='String used as delimiter for one indentation level', + type=parse_num_to_int, + default='2', + help=( + 'The number of indent spaces or a string to be used as delimiter' + ' for indentation level e.g. 4 or "\t" (Default: 2)' + ), ) parser.add_argument( '--no-ensure-ascii', diff --git a/tests/pretty_format_json_test.py b/tests/pretty_format_json_test.py index 4054b4c..7ce7e16 100644 --- a/tests/pretty_format_json_test.py +++ b/tests/pretty_format_json_test.py @@ -3,20 +3,16 @@ import shutil import pytest from six import PY2 -from pre_commit_hooks.pretty_format_json import parse_indent +from pre_commit_hooks.pretty_format_json import parse_num_to_int from pre_commit_hooks.pretty_format_json import pretty_format_json from testing.util import get_resource_path -def test_parse_indent(): - assert parse_indent('0') == '' - assert parse_indent('2') == ' ' - assert parse_indent('\t') == '\t' - with pytest.raises(ValueError): - parse_indent('a') - with pytest.raises(ValueError): - parse_indent('-2') - +def test_parse_num_to_int(): + assert parse_num_to_int('0') == 0 + assert parse_num_to_int('2') == 2 + assert parse_num_to_int('\t') == '\t' + assert parse_num_to_int(' ') == ' ' @pytest.mark.parametrize( From 3e1b954a6e910b5f87bab8b4416084b3fad5fd88 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 11 Dec 2017 07:30:08 -0800 Subject: [PATCH 074/114] Add six back to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index ad14930..8f9550d 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,7 @@ setup( 'flake8!=2.5.3', 'autopep8>=1.3', 'pyyaml', + 'six', ], entry_points={ 'console_scripts': [ From 70ee59f9e61f7fc3a2f79051dc7d6a40a0aaf085 Mon Sep 17 00:00:00 2001 From: nicain Date: Sat, 13 Jan 2018 17:16:50 -0800 Subject: [PATCH 075/114] Update detect_private_key.py --- pre_commit_hooks/detect_private_key.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pre_commit_hooks/detect_private_key.py b/pre_commit_hooks/detect_private_key.py index feb2208..58ed827 100644 --- a/pre_commit_hooks/detect_private_key.py +++ b/pre_commit_hooks/detect_private_key.py @@ -8,6 +8,7 @@ BLACKLIST = [ b'BEGIN DSA PRIVATE KEY', b'BEGIN EC PRIVATE KEY', b'BEGIN OPENSSH PRIVATE KEY', + b'BEGIN PRIVATE KEY', ] From 0ddb3b8527b29e56ce97e5e8308b7a871f53b3a7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 13 Jan 2018 17:36:06 -0800 Subject: [PATCH 076/114] v1.2.0 --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf9d525..14257dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v1.1.1 + sha: v1.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG.md b/CHANGELOG.md index 337739c..73aabbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +1.2.0 +===== +### Features: +- Add new `check-builtin-literals` hook. + - #249 #251 PR by @benwebber. +- `pretty-format-json` no longer depends on `simplejson`. + - #254 PR by @cas--. +- `detect-private-key` now detects gcp keys. + - #255 issue by @SaMnCo @nicain. + - #256 PR by @nicain. + 1.1.1 ===== ### Fixes: diff --git a/README.md b/README.md index 12c86fe..cd69edc 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v1.1.1 # Use the ref you want to point at + sha: v1.2.0 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 8f9550d..662ec62 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='1.1.1', + version='1.2.0', author='Anthony Sottile', author_email='asottile@umich.edu', From 4ab791497cf4829cc9a6fab1dda0b60bd1eae376 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 21 Jan 2018 15:31:23 -0800 Subject: [PATCH 077/114] Replace deprecated yield_fixture with fixture Committed via https://github.com/asottile/all-repos --- tests/check_merge_conflict_test.py | 4 ++-- tests/conftest.py | 2 +- tests/forbid_new_submodules_test.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/check_merge_conflict_test.py b/tests/check_merge_conflict_test.py index a999aca..8a848ba 100644 --- a/tests/check_merge_conflict_test.py +++ b/tests/check_merge_conflict_test.py @@ -12,7 +12,7 @@ from testing.util import get_resource_path from testing.util import write_file -@pytest.yield_fixture +@pytest.fixture def f1_is_a_conflict_file(tmpdir): # Make a merge conflict repo1 = tmpdir.join('repo1') @@ -67,7 +67,7 @@ def f1_is_a_conflict_file(tmpdir): yield -@pytest.yield_fixture +@pytest.fixture def repository_is_pending_merge(tmpdir): # Make a (non-conflicting) merge repo1 = tmpdir.join('repo1') diff --git a/tests/conftest.py b/tests/conftest.py index 87fec70..da206cb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ import pytest from pre_commit_hooks.util import cmd_output -@pytest.yield_fixture +@pytest.fixture def temp_git_dir(tmpdir): git_dir = tmpdir.join('gits') cmd_output('git', 'init', '--', git_dir.strpath) diff --git a/tests/forbid_new_submodules_test.py b/tests/forbid_new_submodules_test.py index fb37862..dc64f06 100644 --- a/tests/forbid_new_submodules_test.py +++ b/tests/forbid_new_submodules_test.py @@ -6,7 +6,7 @@ from pre_commit.util import cmd_output from pre_commit_hooks.forbid_new_submodules import main -@pytest.yield_fixture +@pytest.fixture def git_dir_with_git_dir(tmpdir): with tmpdir.as_cwd(): cmd_output('git', 'init', '.') From 83fca4c614c4bd561b369749821676bba5ce467a Mon Sep 17 00:00:00 2001 From: Pablo Vega Date: Fri, 26 Jan 2018 00:28:39 -0800 Subject: [PATCH 078/114] Adding a check to make sure either spaces or null values are not mistakenly added into the key variable --- pre_commit_hooks/detect_aws_credentials.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py index b2afd71..47a69bf 100644 --- a/pre_commit_hooks/detect_aws_credentials.py +++ b/pre_commit_hooks/detect_aws_credentials.py @@ -53,7 +53,9 @@ def get_aws_secrets_from_file(credentials_file): 'aws_session_token', ): try: - keys.add(parser.get(section, var)) + key = parser.get(section, var).strip() + if key: + keys.add(key) except configparser.NoOptionError: pass return keys From 7c631b3b79217924cbe2c93a14a6f588340f5a4b Mon Sep 17 00:00:00 2001 From: Pablo Vega Date: Fri, 26 Jan 2018 15:19:01 -0800 Subject: [PATCH 079/114] Adding a test for detect_aws_credentials when key contains spaces --- tests/detect_aws_credentials_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/detect_aws_credentials_test.py b/tests/detect_aws_credentials_test.py index 954f3d8..f1bd7d4 100644 --- a/tests/detect_aws_credentials_test.py +++ b/tests/detect_aws_credentials_test.py @@ -83,6 +83,7 @@ def test_get_aws_secrets_from_env(env_vars, values): }, ), ('aws_config_without_secrets.ini', set()), + ('aws_config_without_secrets_with_spaces.ini', set()), ('nonsense.txt', set()), ('ok_json.json', set()), ), @@ -100,6 +101,7 @@ def test_get_aws_secrets_from_file(filename, expected_keys): ('aws_config_with_session_token.ini', 1), ('aws_config_with_multiple_sections.ini', 1), ('aws_config_without_secrets.ini', 0), + ('aws_config_without_secrets_with_spaces.ini', 0), ('nonsense.txt', 0), ('ok_json.json', 0), ), From 97d838f792e08b074eee346c9509f39931e958b9 Mon Sep 17 00:00:00 2001 From: Pablo Vega Date: Fri, 26 Jan 2018 15:22:32 -0800 Subject: [PATCH 080/114] dding the file aws_config_without_secrets_with_spaces.ini for new test case --- testing/resources/aws_config_without_secrets_with_spaces.ini | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 testing/resources/aws_config_without_secrets_with_spaces.ini diff --git a/testing/resources/aws_config_without_secrets_with_spaces.ini b/testing/resources/aws_config_without_secrets_with_spaces.ini new file mode 100644 index 0000000..b169952 --- /dev/null +++ b/testing/resources/aws_config_without_secrets_with_spaces.ini @@ -0,0 +1,4 @@ +# file with an AWS access key id but no valid AWS secret access key only space characters +[production] +aws_access_key_id = AKIASLARTARGENTINA86 +aws_secret_access_key = From 326c18e48700a85b118083b5766ec78ec1283ec7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 1 Feb 2018 11:15:36 -0800 Subject: [PATCH 081/114] Change ignored cache dir for pytest 3.4.0 Committed via https://github.com/asottile/all-repos --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6fdf044..fc72fdb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ *.iml *.py[co] .*.sw[a-z] -.cache +.pytest_cache .coverage .idea .project From 93f319c1f8bcf89aca0e29e886ef16d4e7010e06 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 19 Feb 2018 12:56:14 -0800 Subject: [PATCH 082/114] Fix no-commit-to-branch when not on a branch --- pre_commit_hooks/no_commit_to_branch.py | 14 +++++++++----- tests/check_no_commit_to_branch_test.py | 19 +++++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/pre_commit_hooks/no_commit_to_branch.py b/pre_commit_hooks/no_commit_to_branch.py index 22ee95e..0c75217 100644 --- a/pre_commit_hooks/no_commit_to_branch.py +++ b/pre_commit_hooks/no_commit_to_branch.py @@ -1,21 +1,25 @@ from __future__ import print_function import argparse -import sys +from pre_commit_hooks.util import CalledProcessError from pre_commit_hooks.util import cmd_output def is_on_branch(protected): - branch = cmd_output('git', 'symbolic-ref', 'HEAD') + try: + branch = cmd_output('git', 'symbolic-ref', 'HEAD') + except CalledProcessError: + return False chunks = branch.strip().split('/') return '/'.join(chunks[2:]) == protected -def main(argv=[]): +def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument( - '-b', '--branch', default='master', help='branch to disallow commits to', + '-b', '--branch', default='master', + help='branch to disallow commits to', ) args = parser.parse_args(argv) @@ -23,4 +27,4 @@ def main(argv=[]): if __name__ == '__main__': - sys.exit(main(sys.argv)) + exit(main()) diff --git a/tests/check_no_commit_to_branch_test.py b/tests/check_no_commit_to_branch_test.py index 99af938..7e39256 100644 --- a/tests/check_no_commit_to_branch_test.py +++ b/tests/check_no_commit_to_branch_test.py @@ -29,19 +29,22 @@ def test_master_branch(temp_git_dir): assert is_on_branch('master') is True -def test_main_b_call(temp_git_dir): - with temp_git_dir.as_cwd(): - cmd_output('git', 'checkout', '-b', 'other') - assert main(['-b', 'other']) == 1 - - def test_main_branch_call(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'other') - assert main(['--branch', 'other']) == 1 + assert main(('--branch', 'other')) == 1 def test_main_default_call(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'anotherbranch') - assert main() == 0 + assert main(()) == 0 + + +def test_not_on_a_branch(temp_git_dir): + with temp_git_dir.as_cwd(): + cmd_output('git', 'commit', '--no-gpg-sign', '--allow-empty', '-m1') + head = cmd_output('git', 'rev-parse', 'HEAD').strip() + cmd_output('git', 'checkout', head) + # we're not on a branch! + assert main(()) == 0 From b1a80627506b0bd238a6931f59efa7922b7b2d78 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 19 Feb 2018 13:27:08 -0800 Subject: [PATCH 083/114] v1.2.1 --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 10 ++++++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 14257dc..478dbf9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v1.2.0 + sha: v1.2.1 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG.md b/CHANGELOG.md index 73aabbc..0770077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +1.2.1 +===== +### Fixes: +- `detect-aws-credentials` false positive when key was empty + - #258 issue by @PVSec. + - #260 PR by @PVSec. +- `no-commit-to-branch` no longer crashes when not on a branch + - #265 issue by @hectorv. + - #266 PR by @asottile. + 1.2.0 ===== ### Features: diff --git a/README.md b/README.md index cd69edc..f9d8a59 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v1.2.0 # Use the ref you want to point at + sha: v1.2.1 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 662ec62..a5b3922 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='1.2.0', + version='1.2.1', author='Anthony Sottile', author_email='asottile@umich.edu', From 66eb9d3aec1237b5aae3785b22da6b2f699b5d54 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 24 Feb 2018 09:24:30 -0800 Subject: [PATCH 084/114] Don't pass filenames for no-commit-to-branch --- .pre-commit-hooks.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index f1a901e..7718810 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -228,6 +228,7 @@ name: "Don't commit to branch" entry: no-commit-to-branch language: python + pass_filenames: false always_run: true # for backward compatibility files: '' From 6c9a7e832a81a251f4c5eb7ecb0c2f22fb8346e3 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 24 Feb 2018 09:39:32 -0800 Subject: [PATCH 085/114] v1.2.1-1 --- .pre-commit-config.yaml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 478dbf9..baf84ef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v1.2.1 + sha: v1.2.1-1 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/README.md b/README.md index f9d8a59..f1c42af 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v1.2.1 # Use the ref you want to point at + sha: v1.2.1-1 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... From 2f8b625855c86eece2219fe6910285611c7a4271 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 24 Feb 2018 09:41:00 -0800 Subject: [PATCH 086/114] Add changelog for v1.2.1-1 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0770077..70eec76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +1.2.1-1 +======= + +(Note: this is a tag-only release as no code changes occurred) + +### Fixes: +- Don't pass filenames for `no-commit-to-branch` + - #268 issue by @dongyuzheng. + - #269 PR by @asottile. + 1.2.1 ===== ### Fixes: From 38e02ff508b4c9c047cba82f0f10444b1a2b756d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 28 Feb 2018 08:43:07 -0800 Subject: [PATCH 087/114] Don't add end-of-file newline while trimming whitespace --- pre_commit_hooks/trailing_whitespace_fixer.py | 11 +++-- tests/trailing_whitespace_fixer_test.py | 40 +++++++++---------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/pre_commit_hooks/trailing_whitespace_fixer.py b/pre_commit_hooks/trailing_whitespace_fixer.py index 26f7b51..062f6e3 100644 --- a/pre_commit_hooks/trailing_whitespace_fixer.py +++ b/pre_commit_hooks/trailing_whitespace_fixer.py @@ -19,14 +19,19 @@ def _fix_file(filename, is_markdown): def _process_line(line, is_markdown): + if line[-2:] == b'\r\n': + eol = b'\r\n' + elif line[-1:] == b'\n': + eol = b'\n' + else: + eol = b'' # preserve trailing two-space for non-blank lines in markdown files - eol = b'\r\n' if line[-2:] == b'\r\n' else b'\n' if is_markdown and (not line.isspace()) and line.endswith(b' ' + eol): return line.rstrip() + b' ' + eol return line.rstrip() + eol -def fix_trailing_whitespace(argv=None): +def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument( '--no-markdown-linebreak-ext', @@ -77,4 +82,4 @@ def fix_trailing_whitespace(argv=None): if __name__ == '__main__': - sys.exit(fix_trailing_whitespace()) + sys.exit(main()) diff --git a/tests/trailing_whitespace_fixer_test.py b/tests/trailing_whitespace_fixer_test.py index a771e67..7ee9e63 100644 --- a/tests/trailing_whitespace_fixer_test.py +++ b/tests/trailing_whitespace_fixer_test.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import pytest -from pre_commit_hooks.trailing_whitespace_fixer import fix_trailing_whitespace +from pre_commit_hooks.trailing_whitespace_fixer import main @pytest.mark.parametrize( @@ -16,14 +16,22 @@ from pre_commit_hooks.trailing_whitespace_fixer import fix_trailing_whitespace def test_fixes_trailing_whitespace(input_s, expected, tmpdir): path = tmpdir.join('file.txt') path.write(input_s) - assert fix_trailing_whitespace((path.strpath,)) == 1 + assert main((path.strpath,)) == 1 assert path.read() == expected +def test_ok_no_newline_end_of_file(tmpdir): + filename = tmpdir.join('f') + filename.write_binary(b'foo\nbar') + ret = main((filename.strpath,)) + assert filename.read_binary() == b'foo\nbar' + assert ret == 0 + + def test_ok_with_dos_line_endings(tmpdir): filename = tmpdir.join('f') filename.write_binary(b'foo\r\nbar\r\nbaz\r\n') - ret = fix_trailing_whitespace((filename.strpath,)) + ret = main((filename.strpath,)) assert filename.read_binary() == b'foo\r\nbar\r\nbaz\r\n' assert ret == 0 @@ -31,14 +39,14 @@ def test_ok_with_dos_line_endings(tmpdir): def test_markdown_ok(tmpdir): filename = tmpdir.join('foo.md') filename.write_binary(b'foo \n') - ret = fix_trailing_whitespace((filename.strpath,)) + ret = main((filename.strpath,)) assert filename.read_binary() == b'foo \n' assert ret == 0 # filename, expected input, expected output MD_TESTS_1 = ( - ('foo.md', 'foo \nbar \n ', 'foo \nbar\n\n'), + ('foo.md', 'foo \nbar \n ', 'foo \nbar\n'), ('bar.Markdown', 'bar \nbaz\t\n\t\n', 'bar \nbaz\n\n'), ('.md', 'baz \nquux \t\n\t\n', 'baz\nquux\n\n'), ('txt', 'foo \nbaz \n\t\n', 'foo\nbaz\n\n'), @@ -49,7 +57,7 @@ MD_TESTS_1 = ( def test_fixes_trailing_markdown_whitespace(filename, input_s, output, tmpdir): path = tmpdir.join(filename) path.write(input_s) - ret = fix_trailing_whitespace([path.strpath]) + ret = main([path.strpath]) assert ret == 1 assert path.read() == output @@ -68,16 +76,14 @@ MD_TESTS_2 = ( def test_markdown_linebreak_ext_opt(filename, input_s, output, tmpdir): path = tmpdir.join(filename) path.write(input_s) - ret = fix_trailing_whitespace(( - '--markdown-linebreak-ext=TxT', path.strpath, - )) + ret = main(('--markdown-linebreak-ext=TxT', path.strpath)) assert ret == 1 assert path.read() == output # filename, expected input, expected output MD_TESTS_3 = ( - ('foo.baz', 'foo \nbar \n ', 'foo \nbar\n\n'), + ('foo.baz', 'foo \nbar \n ', 'foo \nbar\n'), ('bar', 'bar \nbaz\t\n\t\n', 'bar \nbaz\n\n'), ) @@ -87,9 +93,7 @@ def test_markdown_linebreak_ext_opt_all(filename, input_s, output, tmpdir): path = tmpdir.join(filename) path.write(input_s) # need to make sure filename is not treated as argument to option - ret = fix_trailing_whitespace([ - '--markdown-linebreak-ext=*', path.strpath, - ]) + ret = main(('--markdown-linebreak-ext=*', path.strpath)) assert ret == 1 assert path.read() == output @@ -97,7 +101,7 @@ def test_markdown_linebreak_ext_opt_all(filename, input_s, output, tmpdir): @pytest.mark.parametrize(('arg'), ('--', 'a.b', 'a/b')) def test_markdown_linebreak_ext_badopt(arg): with pytest.raises(SystemExit) as excinfo: - fix_trailing_whitespace(['--markdown-linebreak-ext', arg]) + main(['--markdown-linebreak-ext', arg]) assert excinfo.value.code == 2 @@ -112,19 +116,15 @@ MD_TESTS_4 = ( def test_no_markdown_linebreak_ext_opt(filename, input_s, output, tmpdir): path = tmpdir.join(filename) path.write(input_s) - ret = fix_trailing_whitespace(['--no-markdown-linebreak-ext', path.strpath]) + ret = main(['--no-markdown-linebreak-ext', path.strpath]) assert ret == 1 assert path.read() == output -def test_returns_zero_for_no_changes(): - assert fix_trailing_whitespace([__file__]) == 0 - - def test_preserve_non_utf8_file(tmpdir): non_utf8_bytes_content = b'\xe9 \n\n' path = tmpdir.join('file.txt') path.write_binary(non_utf8_bytes_content) - ret = fix_trailing_whitespace([path.strpath]) + ret = main([path.strpath]) assert ret == 1 assert path.size() == (len(non_utf8_bytes_content) - 1) From 1b12723c290fd2fe5ff7ad318b5698ec91b6ac47 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 28 Feb 2018 09:01:18 -0800 Subject: [PATCH 088/114] v1.2.2 --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 8 ++++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index baf84ef..94ed36e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v1.2.1-1 + sha: v1.2.2 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG.md b/CHANGELOG.md index 70eec76..2290548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +1.2.2 +===== + +### Fixes +- `trailing-whitespace` no longer adds a missing newline at end-of-file + - #270 issue by @fractos. + - #271 PR by @asottile. + 1.2.1-1 ======= diff --git a/README.md b/README.md index f1c42af..15ba9c5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v1.2.1-1 # Use the ref you want to point at + sha: v1.2.2 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index a5b3922..daee309 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='1.2.1', + version='1.2.2', author='Anthony Sottile', author_email='asottile@umich.edu', From f6780b99f619ff8ef7abad760e28563097c80b31 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 28 Feb 2018 09:13:20 -0800 Subject: [PATCH 089/114] Fix trailing-whitespace entrypoint --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index daee309..20b260d 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ setup( 'pretty-format-json = pre_commit_hooks.pretty_format_json:pretty_format_json', 'requirements-txt-fixer = pre_commit_hooks.requirements_txt_fixer:fix_requirements_txt', 'sort-simple-yaml = pre_commit_hooks.sort_simple_yaml:main', - 'trailing-whitespace-fixer = pre_commit_hooks.trailing_whitespace_fixer:fix_trailing_whitespace', + 'trailing-whitespace-fixer = pre_commit_hooks.trailing_whitespace_fixer:main', ], }, ) From 92e1570c282e3c69a1f8b5b8dd8d286fe27cfaa7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 28 Feb 2018 09:14:40 -0800 Subject: [PATCH 090/114] v1.2.3 --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 7 +++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 94ed36e..48e0673 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v1.2.2 + sha: v1.2.3 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG.md b/CHANGELOG.md index 2290548..04dd781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +1.2.3 +===== + +### Fixes +- `trailing-whitespace` entrypoint was incorrect. + - f6780b9 by @asottile. + 1.2.2 ===== diff --git a/README.md b/README.md index 15ba9c5..8d21a68 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v1.2.2 # Use the ref you want to point at + sha: v1.2.3 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index 20b260d..b9a6a04 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='1.2.2', + version='1.2.3', author='Anthony Sottile', author_email='asottile@umich.edu', From a0a19d66a80c586f55d144f4c91bd9208fb50100 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 8 Mar 2018 22:38:55 -0800 Subject: [PATCH 091/114] Ran pre-commit autoupdate. Committed via https://github.com/asottile/all-repos --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 48e0673..16bbf6c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v1.2.3 + rev: v1.2.3 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -14,19 +14,19 @@ repos: - id: requirements-txt-fixer - id: flake8 - repo: https://github.com/pre-commit/pre-commit - sha: v0.16.3 + rev: v1.7.0 hooks: - id: validate_manifest - repo: https://github.com/asottile/reorder_python_imports - sha: v0.3.5 + rev: v1.0.1 hooks: - id: reorder-python-imports language_version: python2.7 - repo: https://github.com/asottile/pyupgrade - sha: v1.1.4 + rev: v1.2.0 hooks: - id: pyupgrade - repo: https://github.com/asottile/add-trailing-comma - sha: v0.6.4 + rev: v0.6.4 hooks: - id: add-trailing-comma From a21def36e3a4776f36be5f6060d86f0c3116a913 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 19 Mar 2018 09:28:18 -0700 Subject: [PATCH 092/114] Add an `--unsafe` option to `check-yaml` --- README.md | 5 +++++ pre_commit_hooks/check_yaml.py | 39 +++++++++++++++++++++++++++++----- tests/check_yaml_test.py | 21 +++++++++++++++++- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8d21a68..41e9103 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,11 @@ Add this to your `.pre-commit-config.yaml` - `check-yaml` - Attempts to load all yaml files to verify syntax. - `--allow-multiple-documents` - allow yaml files which use the [multi-document syntax](http://www.yaml.org/spec/1.2/spec.html#YAML) + - `--unsafe` - Instaed of loading the files, simply parse them for syntax. + A syntax-only check enables extensions and unsafe constructs which would + otherwise be forbidden. Using this option removes all guarantees of + portability to other yaml implementations. + Implies `--allow-multiple-documents`. - `debug-statements` - Check for pdb / ipdb / pudb statements in code. - `detect-aws-credentials` - Checks for the existence of AWS secrets that you have set up with the AWS CLI. diff --git a/pre_commit_hooks/check_yaml.py b/pre_commit_hooks/check_yaml.py index e9bb8f0..9fbbd88 100644 --- a/pre_commit_hooks/check_yaml.py +++ b/pre_commit_hooks/check_yaml.py @@ -1,6 +1,7 @@ from __future__ import print_function import argparse +import collections import sys import yaml @@ -11,24 +12,52 @@ except ImportError: # pragma: no cover (no libyaml-dev / pypy) Loader = yaml.SafeLoader +def _exhaust(gen): + for _ in gen: + pass + + +def _parse_unsafe(*args, **kwargs): + _exhaust(yaml.parse(*args, **kwargs)) + + def _load_all(*args, **kwargs): - # need to exhaust the generator - return tuple(yaml.load_all(*args, **kwargs)) + _exhaust(yaml.load_all(*args, **kwargs)) + + +Key = collections.namedtuple('Key', ('multi', 'unsafe')) +LOAD_FNS = { + Key(multi=False, unsafe=False): yaml.load, + Key(multi=False, unsafe=True): _parse_unsafe, + Key(multi=True, unsafe=False): _load_all, + Key(multi=True, unsafe=True): _parse_unsafe, +} def check_yaml(argv=None): parser = argparse.ArgumentParser() parser.add_argument( - '-m', '--allow-multiple-documents', dest='yaml_load_fn', - action='store_const', const=_load_all, default=yaml.load, + '-m', '--multi', '--allow-multiple-documents', action='store_true', + ) + parser.add_argument( + '--unsafe', action='store_true', + help=( + 'Instead of loading the files, simply parse them for syntax. ' + 'A syntax-only check enables extensions and unsafe contstructs ' + 'which would otherwise be forbidden. Using this option removes ' + 'all guarantees of portability to other yaml implementations. ' + 'Implies --allow-multiple-documents' + ), ) parser.add_argument('filenames', nargs='*', help='Yaml filenames to check.') args = parser.parse_args(argv) + load_fn = LOAD_FNS[Key(multi=args.multi, unsafe=args.unsafe)] + retval = 0 for filename in args.filenames: try: - args.yaml_load_fn(open(filename), Loader=Loader) + load_fn(open(filename), Loader=Loader) except yaml.YAMLError as exc: print(exc) retval = 1 diff --git a/tests/check_yaml_test.py b/tests/check_yaml_test.py index de3b383..aa357f1 100644 --- a/tests/check_yaml_test.py +++ b/tests/check_yaml_test.py @@ -22,7 +22,7 @@ def test_check_yaml_allow_multiple_documents(tmpdir): f = tmpdir.join('test.yaml') f.write('---\nfoo\n---\nbar\n') - # should failw without the setting + # should fail without the setting assert check_yaml((f.strpath,)) # should pass when we allow multiple documents @@ -33,3 +33,22 @@ def test_fails_even_with_allow_multiple_documents(tmpdir): f = tmpdir.join('test.yaml') f.write('[') assert check_yaml(('--allow-multiple-documents', f.strpath)) + + +def test_check_yaml_unsafe(tmpdir): + f = tmpdir.join('test.yaml') + f.write( + 'some_foo: !vault |\n' + ' $ANSIBLE_VAULT;1.1;AES256\n' + ' deadbeefdeadbeefdeadbeef\n', + ) + # should fail "safe" check + assert check_yaml((f.strpath,)) + # should pass when we allow unsafe documents + assert not check_yaml(('--unsafe', f.strpath)) + + +def test_check_yaml_unsafe_still_fails_on_syntax_errors(tmpdir): + f = tmpdir.join('test.yaml') + f.write('[') + assert check_yaml(('--unsafe', f.strpath)) From 1bdd699a79602760b48e191643a2050a98b35113 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 19 Mar 2018 10:13:18 -0700 Subject: [PATCH 093/114] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41e9103..d650c17 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your `.pre-commit-config.yaml` - `check-yaml` - Attempts to load all yaml files to verify syntax. - `--allow-multiple-documents` - allow yaml files which use the [multi-document syntax](http://www.yaml.org/spec/1.2/spec.html#YAML) - - `--unsafe` - Instaed of loading the files, simply parse them for syntax. + - `--unsafe` - Instead of loading the files, simply parse them for syntax. A syntax-only check enables extensions and unsafe constructs which would otherwise be forbidden. Using this option removes all guarantees of portability to other yaml implementations. From 1d6ad0d6edcb0e75e998537e8cdef3fd6ba7f4ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Socho=C5=84?= Date: Sun, 25 Mar 2018 23:28:04 +0200 Subject: [PATCH 094/114] Provide automatic removal of pkg-resources==0.0.0 Should help to deal with that pretty paintuly issue under Ubuntu/Debian family: https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1635463 --- pre_commit_hooks/requirements_txt_fixer.py | 5 +++++ tests/requirements_txt_fixer_test.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py index 1ee6fac..623fede 100644 --- a/pre_commit_hooks/requirements_txt_fixer.py +++ b/pre_commit_hooks/requirements_txt_fixer.py @@ -69,6 +69,11 @@ def fix_requirements(f): else: rest = [] + for requirement in requirements: + if b'pkg-resources' in requirement.name: + if b'0.0.0' in requirement.value: + requirements.remove(requirement) + for requirement in sorted(requirements): after.extend(requirement.comments) after.append(requirement.value) diff --git a/tests/requirements_txt_fixer_test.py b/tests/requirements_txt_fixer_test.py index 87e7b0c..437cebd 100644 --- a/tests/requirements_txt_fixer_test.py +++ b/tests/requirements_txt_fixer_test.py @@ -28,6 +28,8 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement FAIL, b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n', ), + (b'bar\npkg-resources==0.0.0\nfoo\n', FAIL, b'bar\nfoo\n'), + (b'foo\npkg-resources==0.0.0\nbar\n', FAIL, b'bar\nfoo\n'), ), ) def test_integration(input_s, expected_retval, output, tmpdir): From 9e28aaf2752dc22adaacbd8f65fb8669d3da456a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Socho=C5=84?= Date: Mon, 26 Mar 2018 00:02:23 +0200 Subject: [PATCH 095/114] Simplify check, extend README --- README.md | 2 +- pre_commit_hooks/requirements_txt_fixer.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d650c17..eaf8d3e 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Add this to your `.pre-commit-config.yaml` - `--indent ...` - Control the indentation (either a number for a number of spaces or a string of whitespace). Defaults to 4 spaces. - `--no-sort-keys` - when autofixing, retain the original key ordering (instead of sorting the keys) - `--top-keys comma,separated,keys` - Keys to keep at the top of mappings. -- `requirements-txt-fixer` - Sorts entries in requirements.txt +- `requirements-txt-fixer` - Sorts entries in requirements.txt and removes incorrect entry for `pkg-resources==0.0.0` - `sort-simple-yaml` - Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks. - `trailing-whitespace` - Trims trailing whitespace. - Markdown linebreak trailing spaces preserved for `.md` and`.markdown`; diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py index 623fede..ee432cb 100644 --- a/pre_commit_hooks/requirements_txt_fixer.py +++ b/pre_commit_hooks/requirements_txt_fixer.py @@ -69,10 +69,11 @@ def fix_requirements(f): else: rest = [] + # find and remove pkg-resources==0.0.0 + # which is automatically added by broken pip package under Debian for requirement in requirements: - if b'pkg-resources' in requirement.name: - if b'0.0.0' in requirement.value: - requirements.remove(requirement) + if requirement.value == b'pkg-resources==0.0.0\n': + requirements.remove(requirement) for requirement in sorted(requirements): after.extend(requirement.comments) From b0d44c7084461e23c16798a3dc3d5d65dfd3f97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Socho=C5=84?= Date: Mon, 26 Mar 2018 00:17:13 +0200 Subject: [PATCH 096/114] Ensure not to alter list in 'for' loop --- pre_commit_hooks/requirements_txt_fixer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py index ee432cb..135fe2e 100644 --- a/pre_commit_hooks/requirements_txt_fixer.py +++ b/pre_commit_hooks/requirements_txt_fixer.py @@ -71,9 +71,10 @@ def fix_requirements(f): # find and remove pkg-resources==0.0.0 # which is automatically added by broken pip package under Debian - for requirement in requirements: - if requirement.value == b'pkg-resources==0.0.0\n': - requirements.remove(requirement) + requirements = [ + requirement for requirement in requirements + if requirement.value != b'pkg-resources==0.0.0\n' + ] for requirement in sorted(requirements): after.extend(requirement.comments) From 980fc9bdc302e2afa51c8c02443b51f0dbdc2fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Socho=C5=84?= Date: Mon, 26 Mar 2018 00:24:36 +0200 Subject: [PATCH 097/114] Fix flake8 error Forgot to rename one var... --- pre_commit_hooks/requirements_txt_fixer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py index 135fe2e..6dcf8d0 100644 --- a/pre_commit_hooks/requirements_txt_fixer.py +++ b/pre_commit_hooks/requirements_txt_fixer.py @@ -72,8 +72,8 @@ def fix_requirements(f): # find and remove pkg-resources==0.0.0 # which is automatically added by broken pip package under Debian requirements = [ - requirement for requirement in requirements - if requirement.value != b'pkg-resources==0.0.0\n' + req for req in requirements + if req.value != b'pkg-resources==0.0.0\n' ] for requirement in sorted(requirements): From b312063051e60bf54f7f8fe84285e9bd9340f69b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 25 Mar 2018 17:12:49 -0700 Subject: [PATCH 098/114] Always load the README as UTF-8 --- tests/readme_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/readme_test.py b/tests/readme_test.py index 4d4c972..b2d03b3 100644 --- a/tests/readme_test.py +++ b/tests/readme_test.py @@ -7,7 +7,9 @@ import yaml def test_readme_contains_all_hooks(): - readme_contents = io.open('README.md').read() - hooks = yaml.load(io.open('hooks.yaml').read()) + with io.open('README.md', encoding='UTF-8') as f: + readme_contents = f.read() + with io.open('.pre-commit-hooks.yaml', encoding='UTF-8') as f: + hooks = yaml.load(f) for hook in hooks: assert '`{}`'.format(hook['id']) in readme_contents From 54c0f8c937a739ac8457409affb450a7b2714005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Socho=C5=84?= Date: Mon, 26 Mar 2018 12:00:48 +0200 Subject: [PATCH 099/114] Open files as UTF-8 --- .gitignore | 2 ++ pre_commit_hooks/autopep8_wrapper.py | 4 ++-- pre_commit_hooks/check_docstring_first.py | 2 +- pre_commit_hooks/string_fixer.py | 4 ++-- testing/util.py | 2 +- tests/meta_test.py | 4 ++-- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index fc72fdb..c00e966 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ /venv* coverage-html dist +# SublimeText project/workspace files +*.sublime-* diff --git a/pre_commit_hooks/autopep8_wrapper.py b/pre_commit_hooks/autopep8_wrapper.py index 52aaebe..b2d2d0c 100644 --- a/pre_commit_hooks/autopep8_wrapper.py +++ b/pre_commit_hooks/autopep8_wrapper.py @@ -14,12 +14,12 @@ def main(argv=None): retv = 0 for filename in args.files: - original_contents = io.open(filename).read() + original_contents = io.open(filename, encoding='UTF-8').read() new_contents = autopep8.fix_code(original_contents, args) if original_contents != new_contents: print('Fixing {}'.format(filename)) retv = 1 - with io.open(filename, 'w') as output_file: + with io.open(filename, 'w', encoding='UTF-8') as output_file: output_file.write(new_contents) return retv diff --git a/pre_commit_hooks/check_docstring_first.py b/pre_commit_hooks/check_docstring_first.py index f2f5b72..0896812 100644 --- a/pre_commit_hooks/check_docstring_first.py +++ b/pre_commit_hooks/check_docstring_first.py @@ -58,7 +58,7 @@ def main(argv=None): retv = 0 for filename in args.filenames: - contents = io.open(filename).read() + contents = io.open(filename, encoding='UTF-8').read() retv |= check_docstring_first(contents, filename=filename) return retv diff --git a/pre_commit_hooks/string_fixer.py b/pre_commit_hooks/string_fixer.py index 3523e8a..f73f09d 100644 --- a/pre_commit_hooks/string_fixer.py +++ b/pre_commit_hooks/string_fixer.py @@ -32,7 +32,7 @@ def get_line_offsets_by_line_no(src): def fix_strings(filename): - contents = io.open(filename).read() + contents = io.open(filename, encoding='UTF-8').read() line_offsets = get_line_offsets_by_line_no(contents) # Basically a mutable string @@ -52,7 +52,7 @@ def fix_strings(filename): new_contents = ''.join(splitcontents) if contents != new_contents: - with io.open(filename, 'w') as write_handle: + with io.open(filename, 'w', encoding='UTF-8') as write_handle: write_handle.write(new_contents) return 1 else: diff --git a/testing/util.py b/testing/util.py index 837a3cb..c5e4547 100644 --- a/testing/util.py +++ b/testing/util.py @@ -14,5 +14,5 @@ def get_resource_path(path): def write_file(filename, contents): """Hax because coveragepy chokes on nested context managers.""" - with io.open(filename, 'w', newline='') as file_obj: + with io.open(filename, 'w', encoding='UTF-8', newline='') as file_obj: file_obj.write(contents) diff --git a/tests/meta_test.py b/tests/meta_test.py index 29e06a8..e5d068f 100644 --- a/tests/meta_test.py +++ b/tests/meta_test.py @@ -12,9 +12,9 @@ def _assert_parseable_in_old_pre_commit(hooks): def test_legacy_hooks(): - with io.open('hooks.yaml') as legacy_file: + with io.open('hooks.yaml', encoding='UTF-8') as legacy_file: legacy = yaml.load(legacy_file.read()) - with io.open('.pre-commit-hooks.yaml') as hooks_file: + with io.open('.pre-commit-hooks.yaml', encoding='UTF-8') as hooks_file: hooks = yaml.load(hooks_file.read()) # The same set of hooks should be defined in both files From da8ab1ffe86488e8953ec265dffd221e4b65629d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 26 Mar 2018 08:33:11 -0700 Subject: [PATCH 100/114] pull with --no-rebase --- tests/check_merge_conflict_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/check_merge_conflict_test.py b/tests/check_merge_conflict_test.py index 8a848ba..2460277 100644 --- a/tests/check_merge_conflict_test.py +++ b/tests/check_merge_conflict_test.py @@ -37,7 +37,7 @@ def f1_is_a_conflict_file(tmpdir): with repo2.as_cwd(): repo2_f1.write('child\n') cmd_output('git', 'commit', '--no-gpg-sign', '-am', 'clone commit2') - cmd_output('git', 'pull', retcode=None) + cmd_output('git', 'pull', '--no-rebase', retcode=None) # We should end up in a merge conflict! f1 = repo2_f1.read() assert f1.startswith( From 9b1b44e9c9f934742f35df6de06af53acd7c4be4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 26 Mar 2018 08:44:14 -0700 Subject: [PATCH 101/114] Remove write_file (now unnecessary) --- testing/util.py | 7 ------- tests/check_merge_conflict_test.py | 21 +++++++++------------ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/testing/util.py b/testing/util.py index c5e4547..fac498c 100644 --- a/testing/util.py +++ b/testing/util.py @@ -1,7 +1,6 @@ from __future__ import absolute_import from __future__ import unicode_literals -import io import os.path @@ -10,9 +9,3 @@ TESTING_DIR = os.path.abspath(os.path.dirname(__file__)) def get_resource_path(path): return os.path.join(TESTING_DIR, 'resources', path) - - -def write_file(filename, contents): - """Hax because coveragepy chokes on nested context managers.""" - with io.open(filename, 'w', encoding='UTF-8', newline='') as file_obj: - file_obj.write(contents) diff --git a/tests/check_merge_conflict_test.py b/tests/check_merge_conflict_test.py index 2460277..1045174 100644 --- a/tests/check_merge_conflict_test.py +++ b/tests/check_merge_conflict_test.py @@ -9,7 +9,6 @@ import pytest from pre_commit_hooks.check_merge_conflict import detect_merge_conflict from pre_commit_hooks.util import cmd_output from testing.util import get_resource_path -from testing.util import write_file @pytest.fixture @@ -64,11 +63,11 @@ def f1_is_a_conflict_file(tmpdir): '>>>>>>>', ) assert os.path.exists(os.path.join('.git', 'MERGE_MSG')) - yield + yield repo2 @pytest.fixture -def repository_is_pending_merge(tmpdir): +def repository_pending_merge(tmpdir): # Make a (non-conflicting) merge repo1 = tmpdir.join('repo1') repo1_f1 = repo1.join('f1') @@ -98,7 +97,7 @@ def repository_is_pending_merge(tmpdir): assert repo2_f1.read() == 'parent\n' assert repo2_f2.read() == 'child\n' assert os.path.exists(os.path.join('.git', 'MERGE_HEAD')) - yield + yield repo2 @pytest.mark.usefixtures('f1_is_a_conflict_file') @@ -107,20 +106,18 @@ def test_merge_conflicts_git(): @pytest.mark.parametrize( - 'failing_contents', ('<<<<<<< HEAD\n', '=======\n', '>>>>>>> master\n'), + 'contents', (b'<<<<<<< HEAD\n', b'=======\n', b'>>>>>>> master\n'), ) -@pytest.mark.usefixtures('repository_is_pending_merge') -def test_merge_conflicts_failing(failing_contents): - write_file('f2', failing_contents) +def test_merge_conflicts_failing(contents, repository_pending_merge): + repository_pending_merge.join('f2').write_binary(contents) assert detect_merge_conflict(['f2']) == 1 @pytest.mark.parametrize( - 'ok_contents', ('# <<<<<<< HEAD\n', '# =======\n', 'import my_module', ''), + 'contents', (b'# <<<<<<< HEAD\n', b'# =======\n', b'import mod', b''), ) -@pytest.mark.usefixtures('f1_is_a_conflict_file') -def test_merge_conflicts_ok(ok_contents): - write_file('f1', ok_contents) +def test_merge_conflicts_ok(contents, f1_is_a_conflict_file): + f1_is_a_conflict_file.join('f1').write_binary(contents) assert detect_merge_conflict(['f1']) == 0 From 3a325ee786aea772fff23e1c87aaccfa000f03f1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 30 Apr 2018 09:33:25 -0400 Subject: [PATCH 102/114] Replace legacy wheel metadata Committed via https://github.com/asottile/all-repos --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e57d130..2be6836 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ -[wheel] +[bdist_wheel] universal = True From 6e2e83a40933767d67b3f5544585ad8bc2b7ea25 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 13 May 2018 15:07:31 -0700 Subject: [PATCH 103/114] Simplify debug-statemetns tests --- testing/resources/file_with_debug.notpy | 5 -- tests/debug_statement_hook_test.py | 62 +++++-------------------- 2 files changed, 12 insertions(+), 55 deletions(-) delete mode 100644 testing/resources/file_with_debug.notpy diff --git a/testing/resources/file_with_debug.notpy b/testing/resources/file_with_debug.notpy deleted file mode 100644 index faa23a2..0000000 --- a/testing/resources/file_with_debug.notpy +++ /dev/null @@ -1,5 +0,0 @@ - -def foo(obj): - import pdb; pdb.set_trace() - - return 5 diff --git a/tests/debug_statement_hook_test.py b/tests/debug_statement_hook_test.py index 8832245..44c2011 100644 --- a/tests/debug_statement_hook_test.py +++ b/tests/debug_statement_hook_test.py @@ -4,72 +4,34 @@ from __future__ import unicode_literals import ast -import pytest - from pre_commit_hooks.debug_statement_hook import debug_statement_hook from pre_commit_hooks.debug_statement_hook import DebugStatement from pre_commit_hooks.debug_statement_hook import ImportStatementParser from testing.util import get_resource_path -@pytest.fixture -def ast_with_no_debug_imports(): - return ast.parse( - """ -import foo -import bar -import baz -from foo import bar -""", - ) - - -@pytest.fixture -def ast_with_debug_import_form_1(): - return ast.parse( - """ - -import ipdb; ipdb.set_trace() - -""", - ) - - -@pytest.fixture -def ast_with_debug_import_form_2(): - return ast.parse( - """ - -from pudb import set_trace; set_trace() - -""", - ) - - -def test_returns_no_debug_statements(ast_with_no_debug_imports): +def test_no_debug_imports(): visitor = ImportStatementParser() - visitor.visit(ast_with_no_debug_imports) + visitor.visit(ast.parse('import os\nfrom foo import bar\n')) assert visitor.debug_import_statements == [] -def test_returns_one_form_1(ast_with_debug_import_form_1): +def test_finds_debug_import_attribute_access(): visitor = ImportStatementParser() - visitor.visit(ast_with_debug_import_form_1) - assert visitor.debug_import_statements == [ - DebugStatement('ipdb', 3, 0), - ] + visitor.visit(ast.parse('import ipdb; ipdb.set_trace()')) + assert visitor.debug_import_statements == [DebugStatement('ipdb', 1, 0)] -def test_returns_one_form_2(ast_with_debug_import_form_2): +def test_finds_debug_import_from_import(): visitor = ImportStatementParser() - visitor.visit(ast_with_debug_import_form_2) - assert visitor.debug_import_statements == [ - DebugStatement('pudb', 3, 0), - ] + visitor.visit(ast.parse('from pudb import set_trace; set_trace()')) + assert visitor.debug_import_statements == [DebugStatement('pudb', 1, 0)] -def test_returns_one_for_failing_file(): - ret = debug_statement_hook([get_resource_path('file_with_debug.notpy')]) +def test_returns_one_for_failing_file(tmpdir): + f_py = tmpdir.join('f.py') + f_py.write('def f():\n import pdb; pdb.set_trace()') + ret = debug_statement_hook([f_py.strpath]) assert ret == 1 From 18b3ab7cd2562427da76c61e30ec78493e6df3ce Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 13 May 2018 15:24:42 -0700 Subject: [PATCH 104/114] debug-statements: detect python3.7+ breakpoint() --- pre_commit_hooks/debug_statement_hook.py | 65 +++++++++++------------- setup.py | 2 +- tests/debug_statement_hook_test.py | 40 ++++++++++----- 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/pre_commit_hooks/debug_statement_hook.py b/pre_commit_hooks/debug_statement_hook.py index c5ca387..81591dd 100644 --- a/pre_commit_hooks/debug_statement_hook.py +++ b/pre_commit_hooks/debug_statement_hook.py @@ -8,32 +8,33 @@ import traceback DEBUG_STATEMENTS = {'pdb', 'ipdb', 'pudb', 'q', 'rdb'} +Debug = collections.namedtuple('Debug', ('line', 'col', 'name', 'reason')) -DebugStatement = collections.namedtuple( - 'DebugStatement', ['name', 'line', 'col'], -) - - -class ImportStatementParser(ast.NodeVisitor): +class DebugStatementParser(ast.NodeVisitor): def __init__(self): - self.debug_import_statements = [] + self.breakpoints = [] def visit_Import(self, node): - for node_name in node.names: - if node_name.name in DEBUG_STATEMENTS: - self.debug_import_statements.append( - DebugStatement(node_name.name, node.lineno, node.col_offset), - ) + for name in node.names: + if name.name in DEBUG_STATEMENTS: + st = Debug(node.lineno, node.col_offset, name.name, 'imported') + self.breakpoints.append(st) def visit_ImportFrom(self, node): if node.module in DEBUG_STATEMENTS: - self.debug_import_statements.append( - DebugStatement(node.module, node.lineno, node.col_offset), - ) + st = Debug(node.lineno, node.col_offset, node.module, 'imported') + self.breakpoints.append(st) + + def visit_Call(self, node): + """python3.7+ breakpoint()""" + if isinstance(node.func, ast.Name) and node.func.id == 'breakpoint': + st = Debug(node.lineno, node.col_offset, node.func.id, 'called') + self.breakpoints.append(st) + self.generic_visit(node) -def check_file_for_debug_statements(filename): +def check_file(filename): try: ast_obj = ast.parse(open(filename, 'rb').read(), filename=filename) except SyntaxError: @@ -42,34 +43,30 @@ def check_file_for_debug_statements(filename): print('\t' + traceback.format_exc().replace('\n', '\n\t')) print() return 1 - visitor = ImportStatementParser() + + visitor = DebugStatementParser() visitor.visit(ast_obj) - if visitor.debug_import_statements: - for debug_statement in visitor.debug_import_statements: - print( - '{}:{}:{} - {} imported'.format( - filename, - debug_statement.line, - debug_statement.col, - debug_statement.name, - ), - ) - return 1 - else: - return 0 + + for bp in visitor.breakpoints: + print( + '{}:{}:{} - {} {}'.format( + filename, bp.line, bp.col, bp.name, bp.reason, + ), + ) + + return int(bool(visitor.breakpoints)) -def debug_statement_hook(argv=None): +def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument('filenames', nargs='*', help='Filenames to run') args = parser.parse_args(argv) retv = 0 for filename in args.filenames: - retv |= check_file_for_debug_statements(filename) - + retv |= check_file(filename) return retv if __name__ == '__main__': - exit(debug_statement_hook()) + exit(main()) diff --git a/setup.py b/setup.py index b9a6a04..a6f7a52 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ setup( 'check-vcs-permalinks = pre_commit_hooks.check_vcs_permalinks:main', 'check-xml = pre_commit_hooks.check_xml:check_xml', 'check-yaml = pre_commit_hooks.check_yaml:check_yaml', - 'debug-statement-hook = pre_commit_hooks.debug_statement_hook:debug_statement_hook', + 'debug-statement-hook = pre_commit_hooks.debug_statement_hook:main', 'detect-aws-credentials = pre_commit_hooks.detect_aws_credentials:main', 'detect-private-key = pre_commit_hooks.detect_private_key:detect_private_key', 'double-quote-string-fixer = pre_commit_hooks.string_fixer:main', diff --git a/tests/debug_statement_hook_test.py b/tests/debug_statement_hook_test.py index 44c2011..d15f5f7 100644 --- a/tests/debug_statement_hook_test.py +++ b/tests/debug_statement_hook_test.py @@ -4,48 +4,60 @@ from __future__ import unicode_literals import ast -from pre_commit_hooks.debug_statement_hook import debug_statement_hook -from pre_commit_hooks.debug_statement_hook import DebugStatement -from pre_commit_hooks.debug_statement_hook import ImportStatementParser +from pre_commit_hooks.debug_statement_hook import Debug +from pre_commit_hooks.debug_statement_hook import DebugStatementParser +from pre_commit_hooks.debug_statement_hook import main from testing.util import get_resource_path -def test_no_debug_imports(): - visitor = ImportStatementParser() +def test_no_breakpoints(): + visitor = DebugStatementParser() visitor.visit(ast.parse('import os\nfrom foo import bar\n')) - assert visitor.debug_import_statements == [] + assert visitor.breakpoints == [] def test_finds_debug_import_attribute_access(): - visitor = ImportStatementParser() + visitor = DebugStatementParser() visitor.visit(ast.parse('import ipdb; ipdb.set_trace()')) - assert visitor.debug_import_statements == [DebugStatement('ipdb', 1, 0)] + assert visitor.breakpoints == [Debug(1, 0, 'ipdb', 'imported')] def test_finds_debug_import_from_import(): - visitor = ImportStatementParser() + visitor = DebugStatementParser() visitor.visit(ast.parse('from pudb import set_trace; set_trace()')) - assert visitor.debug_import_statements == [DebugStatement('pudb', 1, 0)] + assert visitor.breakpoints == [Debug(1, 0, 'pudb', 'imported')] + + +def test_finds_breakpoint(): + visitor = DebugStatementParser() + visitor.visit(ast.parse('breakpoint()')) + assert visitor.breakpoints == [Debug(1, 0, 'breakpoint', 'called')] def test_returns_one_for_failing_file(tmpdir): f_py = tmpdir.join('f.py') f_py.write('def f():\n import pdb; pdb.set_trace()') - ret = debug_statement_hook([f_py.strpath]) + ret = main([f_py.strpath]) assert ret == 1 def test_returns_zero_for_passing_file(): - ret = debug_statement_hook([__file__]) + ret = main([__file__]) assert ret == 0 def test_syntaxerror_file(): - ret = debug_statement_hook([get_resource_path('cannot_parse_ast.notpy')]) + ret = main([get_resource_path('cannot_parse_ast.notpy')]) assert ret == 1 def test_non_utf8_file(tmpdir): f_py = tmpdir.join('f.py') f_py.write_binary('# -*- coding: cp1252 -*-\nx = "€"\n'.encode('cp1252')) - assert debug_statement_hook((f_py.strpath,)) == 0 + assert main((f_py.strpath,)) == 0 + + +def test_py37_breakpoint(tmpdir): + f_py = tmpdir.join('f.py') + f_py.write('def f():\n breakpoint()\n') + assert main((f_py.strpath,)) == 1 From 805530fe2934a55dc399f80615725157dec5f062 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 14 May 2018 09:16:37 -0700 Subject: [PATCH 105/114] Update debug-statements description --- .pre-commit-hooks.yaml | 2 +- README.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 7718810..1f62b3d 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -134,7 +134,7 @@ minimum_pre_commit_version: 0.15.0 - id: debug-statements name: Debug Statements (Python) - description: This hook checks that debug statements (pdb, ipdb, pudb) are not imported on commit. + description: Check for debugger imports and py37+ `breakpoint()` calls in python source. entry: debug-statement-hook language: python types: [python] diff --git a/README.md b/README.md index eaf8d3e..343ebcd 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ Add this to your `.pre-commit-config.yaml` otherwise be forbidden. Using this option removes all guarantees of portability to other yaml implementations. Implies `--allow-multiple-documents`. -- `debug-statements` - Check for pdb / ipdb / pudb statements in code. +- `debug-statements` - Check for debugger imports and py37+ `breakpoint()` + calls in python source. - `detect-aws-credentials` - Checks for the existence of AWS secrets that you have set up with the AWS CLI. The following arguments are available: From df93509aed538881f56edb527de8f6f8bb25b998 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 17 May 2018 17:14:25 -0700 Subject: [PATCH 106/114] Explicitly check for `ast.Name` --- pre_commit_hooks/check_builtin_literals.py | 2 +- tests/check_builtin_literals_test.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pre_commit_hooks/check_builtin_literals.py b/pre_commit_hooks/check_builtin_literals.py index c4ac969..b7f0c00 100644 --- a/pre_commit_hooks/check_builtin_literals.py +++ b/pre_commit_hooks/check_builtin_literals.py @@ -30,7 +30,7 @@ class BuiltinTypeVisitor(ast.NodeVisitor): return self.allow_dict_kwargs and (getattr(node, 'kwargs', None) or getattr(node, 'keywords', None)) def visit_Call(self, node): - if isinstance(node.func, ast.Attribute): + if not isinstance(node.func, ast.Name): # Ignore functions that are object attributes (`foo.bar()`). # Assume that if the user calls `builtins.list()`, they know what # they're doing. diff --git a/tests/check_builtin_literals_test.py b/tests/check_builtin_literals_test.py index 13f896a..5ab162e 100644 --- a/tests/check_builtin_literals_test.py +++ b/tests/check_builtin_literals_test.py @@ -16,6 +16,8 @@ def visitor(): @pytest.mark.parametrize( ('expression', 'calls'), [ + # see #285 + ('x[0]()', []), # complex ("0j", []), ("complex()", [BuiltinTypeCall('complex', 1, 0)]), From 45d38502447817765d0cf255fff670b0d9ab64b5 Mon Sep 17 00:00:00 2001 From: vin01 Date: Sat, 19 May 2018 00:31:16 +0100 Subject: [PATCH 107/114] Detect putty & sshcom private keys --- pre_commit_hooks/detect_private_key.py | 2 ++ tests/detect_private_key_test.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pre_commit_hooks/detect_private_key.py b/pre_commit_hooks/detect_private_key.py index 58ed827..693e59e 100644 --- a/pre_commit_hooks/detect_private_key.py +++ b/pre_commit_hooks/detect_private_key.py @@ -9,6 +9,8 @@ BLACKLIST = [ b'BEGIN EC PRIVATE KEY', b'BEGIN OPENSSH PRIVATE KEY', b'BEGIN PRIVATE KEY', + b'PuTTY-User-Key-File-2', + b'BEGIN SSH2 ENCRYPTED PRIVATE KEY', ] diff --git a/tests/detect_private_key_test.py b/tests/detect_private_key_test.py index c6558ba..fdd63a2 100644 --- a/tests/detect_private_key_test.py +++ b/tests/detect_private_key_test.py @@ -8,6 +8,8 @@ TESTS = ( (b'-----BEGIN DSA PRIVATE KEY-----', 1), (b'-----BEGIN EC PRIVATE KEY-----', 1), (b'-----BEGIN OPENSSH PRIVATE KEY-----', 1), + (b'PuTTY-User-Key-File-2: ssh-rsa', 1), + (b'---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----', 1), (b'ssh-rsa DATA', 0), (b'ssh-dsa DATA', 0), # Some arbitrary binary data From e8a9a519d233ba99a6d78c10a7f7a95f23c0fb7e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 23 May 2018 17:45:46 -0700 Subject: [PATCH 108/114] Remove (unused) Makefile Committed via https://github.com/asottile/all-repos --- Makefile | 24 ------------------------ tox.ini | 5 ----- 2 files changed, 29 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index b38b1ae..0000000 --- a/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -REBUILD_FLAG = - -.PHONY: all -all: venv test - -.PHONY: venv -venv: .venv.touch - tox -e venv $(REBUILD_FLAG) - -.PHONY: tests test -tests: test -test: .venv.touch - tox $(REBUILD_FLAG) - -.venv.touch: setup.py requirements-dev.txt - $(eval REBUILD_FLAG := --recreate) - touch .venv.touch - -.PHONY: clean -clean: - find . -name '*.pyc' -delete - rm -rf .tox - rm -rf ./venv-* - rm -f .venv.touch diff --git a/tox.ini b/tox.ini index d0863eb..06c3588 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,4 @@ [tox] -project = pre_commit_hooks # These should match the travis env list envlist = py27,py35,py36,pypy @@ -18,10 +17,6 @@ commands = pre-commit install -f --install-hooks pre-commit run --all-files -[testenv:venv] -envdir = venv-{[tox]project} -commands = - [flake8] max-line-length=131 From a6209d8d4f97a09b61855ea3f1fb250f55147b8b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 28 May 2018 10:34:46 -0700 Subject: [PATCH 109/114] v1.3.0 --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 22 ++++++++++++++++++++++ README.md | 2 +- setup.py | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 16bbf6c..e657ccb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v1.2.3 + rev: v1.3.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG.md b/CHANGELOG.md index 04dd781..b58ddbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +1.3.0 +===== + +### Features +- Add an `--unsafe` argument to `check-yaml` to allow custom yaml tags + - #273 issue by @blackillzone. + - #274 PR by @asottile. +- Automatically remove `pkg-resources==0.0.0` in `requirements-txt-fixer` + - #275 PR by @nvtkaszpir. +- Detect `breakpoint()` (python3.7+) in `debug-statements` hook. + - #283 PR by @asottile. +- Detect sshcom and putty hooks in `detect-private-key` + - #287 PR by @vin01. + +### Fixes +- Open files as UTF-8 (`autopep8-wrapper`, `check-docstring-first`, + `double-quote-string-fixer`) + - #279 PR by @nvtkaszpir. +- Fix `AttributeError` in `check-builtin-literals` for some functions + - #285 issue by @EgoWumpus. + - #286 PR by @asottile. + 1.2.3 ===== diff --git a/README.md b/README.md index 343ebcd..ae1eb54 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - repo: git://github.com/pre-commit/pre-commit-hooks - sha: v1.2.3 # Use the ref you want to point at + rev: v1.3.0 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... diff --git a/setup.py b/setup.py index a6f7a52..bc10172 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pre_commit_hooks', description='Some out-of-the-box hooks for pre-commit.', url='https://github.com/pre-commit/pre-commit-hooks', - version='1.2.3', + version='1.3.0', author='Anthony Sottile', author_email='asottile@umich.edu', From ffcda22d777dca8ef13f61357b23005fa1b02cf0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 28 May 2018 10:58:28 -0700 Subject: [PATCH 110/114] Suggest https:// git urls Committed via https://github.com/asottile/all-repos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae1eb54..6a6af91 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ See also: https://github.com/pre-commit/pre-commit Add this to your `.pre-commit-config.yaml` - - repo: git://github.com/pre-commit/pre-commit-hooks + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v1.3.0 # Use the ref you want to point at hooks: - id: trailing-whitespace From d12ed2f637c48ea3748fe08ea49342e9c6bcd6d2 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 1 Jun 2018 13:39:48 -0700 Subject: [PATCH 111/114] E309 is no longer rewritten by autopep8 Committed via https://github.com/asottile/all-repos --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 06c3588..0b33d3e 100644 --- a/tox.ini +++ b/tox.ini @@ -21,4 +21,4 @@ commands = max-line-length=131 [pep8] -ignore=E265,E309,E501 +ignore=E265,E501 From c72ad40a6d9337a6a338520bc988317e1ec0df50 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 4 Jun 2018 10:10:14 -0700 Subject: [PATCH 112/114] Improve vcs regex (don't match whitespace) --- pre_commit_hooks/check_vcs_permalinks.py | 2 +- tests/check_vcs_permalinks_test.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pre_commit_hooks/check_vcs_permalinks.py b/pre_commit_hooks/check_vcs_permalinks.py index d67d5f5..139b41a 100644 --- a/pre_commit_hooks/check_vcs_permalinks.py +++ b/pre_commit_hooks/check_vcs_permalinks.py @@ -8,7 +8,7 @@ import sys GITHUB_NON_PERMALINK = re.compile( - b'https://github.com/[^/]+/[^/]+/blob/master/[^# ]+#L\d+', + b'https://github.com/[^/ ]+/[^/ ]+/blob/master/[^# ]+#L\d+', ) diff --git a/tests/check_vcs_permalinks_test.py b/tests/check_vcs_permalinks_test.py index 31fd608..00e5396 100644 --- a/tests/check_vcs_permalinks_test.py +++ b/tests/check_vcs_permalinks_test.py @@ -15,7 +15,9 @@ def test_passing(tmpdir): # permalinks are ok b'https://github.com/asottile/test/blob/649e6/foo%20bar#L1\n' # links to files but not line numbers are ok - b'https://github.com/asottile/test/blob/master/foo%20bar\n', + b'https://github.com/asottile/test/blob/master/foo%20bar\n' + # regression test for overly-greedy regex + b'https://github.com/ yes / no ? /blob/master/foo#L1\n', ) assert not main((f.strpath,)) From 2aa7aeb572c1297d25a45b2e137b72e235b510e4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 9 Jun 2018 11:10:43 -0700 Subject: [PATCH 113/114] git mv tests/{check_,}no_commit_to_branch_test.py --- ...ck_no_commit_to_branch_test.py => no_commit_to_branch_test.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{check_no_commit_to_branch_test.py => no_commit_to_branch_test.py} (100%) diff --git a/tests/check_no_commit_to_branch_test.py b/tests/no_commit_to_branch_test.py similarity index 100% rename from tests/check_no_commit_to_branch_test.py rename to tests/no_commit_to_branch_test.py From baec30836715238f66bedb3f49484c2a477a32eb Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 9 Jun 2018 11:16:14 -0700 Subject: [PATCH 114/114] Allow multiple branches to be protected Original patch by @moas --- README.md | 5 ++++- pre_commit_hooks/no_commit_to_branch.py | 9 +++++---- tests/no_commit_to_branch_test.py | 17 +++++++++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6a6af91..82830f4 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,10 @@ Add this to your `.pre-commit-config.yaml` - `name-tests-test` - Assert that files in tests/ end in `_test.py`. - Use `args: ['--django']` to match `test*.py` instead. - `no-commit-to-branch` - Protect specific branches from direct checkins. - - Use `args: -b ` to set the branch. `master` is the default if no argument is set. + - Use `args: [--branch ]` to set the branch. `master` is the + default if no argument is set. + - `-b` / `--branch` may be specified multiple times to protect multiple + branches. - `pyflakes` - Run pyflakes on your python files. - `pretty-format-json` - Checks that all your JSON files are pretty. "Pretty" here means that keys are sorted and indented. You can configure this with diff --git a/pre_commit_hooks/no_commit_to_branch.py b/pre_commit_hooks/no_commit_to_branch.py index 0c75217..fdd146b 100644 --- a/pre_commit_hooks/no_commit_to_branch.py +++ b/pre_commit_hooks/no_commit_to_branch.py @@ -12,18 +12,19 @@ def is_on_branch(protected): except CalledProcessError: return False chunks = branch.strip().split('/') - return '/'.join(chunks[2:]) == protected + return '/'.join(chunks[2:]) in protected def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument( - '-b', '--branch', default='master', - help='branch to disallow commits to', + '-b', '--branch', action='append', + help='branch to disallow commits to, may be specified multiple times', ) args = parser.parse_args(argv) - return int(is_on_branch(args.branch)) + protected = set(args.branch or ('master',)) + return int(is_on_branch(protected)) if __name__ == '__main__': diff --git a/tests/no_commit_to_branch_test.py b/tests/no_commit_to_branch_test.py index 7e39256..c275bf7 100644 --- a/tests/no_commit_to_branch_test.py +++ b/tests/no_commit_to_branch_test.py @@ -1,6 +1,8 @@ from __future__ import absolute_import from __future__ import unicode_literals +import pytest + from pre_commit_hooks.no_commit_to_branch import is_on_branch from pre_commit_hooks.no_commit_to_branch import main from pre_commit_hooks.util import cmd_output @@ -9,24 +11,24 @@ from pre_commit_hooks.util import cmd_output def test_other_branch(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'anotherbranch') - assert is_on_branch('master') is False + assert is_on_branch(('master',)) is False def test_multi_branch(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'another/branch') - assert is_on_branch('master') is False + assert is_on_branch(('master',)) is False def test_multi_branch_fail(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'another/branch') - assert is_on_branch('another/branch') is True + assert is_on_branch(('another/branch',)) is True def test_master_branch(temp_git_dir): with temp_git_dir.as_cwd(): - assert is_on_branch('master') is True + assert is_on_branch(('master',)) is True def test_main_branch_call(temp_git_dir): @@ -35,6 +37,13 @@ def test_main_branch_call(temp_git_dir): assert main(('--branch', 'other')) == 1 +@pytest.mark.parametrize('branch_name', ('b1', 'b2')) +def test_forbid_multiple_branches(temp_git_dir, branch_name): + with temp_git_dir.as_cwd(): + cmd_output('git', 'checkout', '-b', branch_name) + assert main(('--branch', 'b1', '--branch', 'b2')) + + def test_main_default_call(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'anotherbranch')