From ef7c425a3ef9c8bfcf2640a393aea363fa8c8172 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 13 Mar 2017 14:32:43 -0700 Subject: [PATCH 01/27] 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 6076fd1b157f5679276da6c0b6dfd413d841387c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2017 10:39:07 -0700 Subject: [PATCH 02/27] 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 03/27] 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 04/27] 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 05/27] 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 06/27] 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 07/27] 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 08/27] 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 09/27] 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 10/27] 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 11/27] 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 12/27] 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 13/27] 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 14/27] 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 15/27] 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 16/27] 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 17/27] 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 18/27] 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 19/27] 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 20/27] 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 21/27] 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 22/27] 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 23/27] 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 24/27] 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 25/27] 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 26/27] 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 27/27] 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'}, ), ), )