mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-03-30 18:26:53 +00:00
Compare commits
No commits in common. "main" and "v2.5.0" have entirely different histories.
87 changed files with 1300 additions and 2286 deletions
29
.coveragerc
Normal file
29
.coveragerc
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
[run]
|
||||||
|
branch = True
|
||||||
|
source =
|
||||||
|
.
|
||||||
|
omit =
|
||||||
|
.tox/*
|
||||||
|
/usr/*
|
||||||
|
setup.py
|
||||||
|
|
||||||
|
[report]
|
||||||
|
show_missing = True
|
||||||
|
skip_covered = True
|
||||||
|
exclude_lines =
|
||||||
|
# Have to re-enable the standard pragma
|
||||||
|
\#\s*pragma: no cover
|
||||||
|
|
||||||
|
# Don't complain if tests don't hit defensive assertion code:
|
||||||
|
^\s*raise AssertionError\b
|
||||||
|
^\s*raise NotImplementedError\b
|
||||||
|
^\s*return NotImplemented\b
|
||||||
|
^\s*raise$
|
||||||
|
|
||||||
|
# Don't complain if non-runnable code isn't run:
|
||||||
|
^if __name__ == ['"]__main__['"]:$
|
||||||
|
|
||||||
|
[html]
|
||||||
|
directory = coverage-html
|
||||||
|
|
||||||
|
# vim:ft=dosini
|
||||||
19
.github/workflows/main.yml
vendored
19
.github/workflows/main.yml
vendored
|
|
@ -1,19 +0,0 @@
|
||||||
name: main
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, test-me-*]
|
|
||||||
tags: '*'
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
main-windows:
|
|
||||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
|
|
||||||
with:
|
|
||||||
env: '["py310"]'
|
|
||||||
os: windows-latest
|
|
||||||
main-linux:
|
|
||||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
|
|
||||||
with:
|
|
||||||
env: '["py310", "py311", "py312", "py313"]'
|
|
||||||
os: ubuntu-latest
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -3,4 +3,9 @@
|
||||||
.*.sw[a-z]
|
.*.sw[a-z]
|
||||||
.coverage
|
.coverage
|
||||||
.tox
|
.tox
|
||||||
|
.venv.touch
|
||||||
|
/.mypy_cache
|
||||||
|
/.pytest_cache
|
||||||
|
/venv*
|
||||||
|
coverage-html
|
||||||
dist
|
dist
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,44 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v6.0.0
|
rev: v2.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
- id: check-docstring-first
|
||||||
|
- id: check-json
|
||||||
|
- id: check-added-large-files
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: double-quote-string-fixer
|
|
||||||
- id: name-tests-test
|
- id: name-tests-test
|
||||||
|
- id: double-quote-string-fixer
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
rev: v3.2.0
|
rev: 3.7.9
|
||||||
hooks:
|
|
||||||
- id: setup-cfg-fmt
|
|
||||||
- repo: https://github.com/asottile/reorder-python-imports
|
|
||||||
rev: v3.16.0
|
|
||||||
hooks:
|
|
||||||
- id: reorder-python-imports
|
|
||||||
args: [--py310-plus, --add-import, 'from __future__ import annotations']
|
|
||||||
- repo: https://github.com/asottile/add-trailing-comma
|
|
||||||
rev: v4.0.0
|
|
||||||
hooks:
|
|
||||||
- id: add-trailing-comma
|
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
|
||||||
rev: v3.21.2
|
|
||||||
hooks:
|
|
||||||
- id: pyupgrade
|
|
||||||
args: [--py310-plus]
|
|
||||||
- repo: https://github.com/hhatto/autopep8
|
|
||||||
rev: v2.3.2
|
|
||||||
hooks:
|
|
||||||
- id: autopep8
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
|
||||||
rev: 7.3.0
|
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-autopep8
|
||||||
|
rev: v1.5
|
||||||
|
hooks:
|
||||||
|
- id: autopep8
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit
|
||||||
|
rev: v2.0.1
|
||||||
|
hooks:
|
||||||
|
- id: validate_manifest
|
||||||
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
|
rev: v1.9.0
|
||||||
|
hooks:
|
||||||
|
- id: reorder-python-imports
|
||||||
|
language_version: python3
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v1.26.2
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
- repo: https://github.com/asottile/add-trailing-comma
|
||||||
|
rev: v1.5.0
|
||||||
|
hooks:
|
||||||
|
- id: add-trailing-comma
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v1.19.1
|
rev: v0.761
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
|
language_version: python3
|
||||||
|
|
|
||||||
|
|
@ -1,212 +1,194 @@
|
||||||
|
- id: autopep8-wrapper
|
||||||
|
name: autopep8 wrapper
|
||||||
|
description: This is deprecated, use pre-commit/mirrors-autopep8 instead.
|
||||||
|
entry: autopep8-wrapper
|
||||||
|
language: python
|
||||||
|
types: [python]
|
||||||
|
args: [-i]
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
name: check for added large files
|
name: Check for added large files
|
||||||
description: prevents giant files from being committed.
|
description: Prevent giant files from being committed
|
||||||
entry: check-added-large-files
|
entry: check-added-large-files
|
||||||
language: python
|
language: python
|
||||||
stages: [pre-commit, pre-push, manual]
|
|
||||||
minimum_pre_commit_version: 3.2.0
|
|
||||||
- id: check-ast
|
- id: check-ast
|
||||||
name: check python ast
|
name: Check python ast
|
||||||
description: simply checks whether the files parse as valid python.
|
description: Simply check whether the files parse as valid python.
|
||||||
entry: check-ast
|
entry: check-ast
|
||||||
language: python
|
language: python
|
||||||
types: [python]
|
types: [python]
|
||||||
- id: check-byte-order-marker
|
- id: check-byte-order-marker
|
||||||
name: check-byte-order-marker (removed)
|
name: Check for byte-order marker
|
||||||
description: (removed) use fix-byte-order-marker instead.
|
description: Forbid files which have a UTF-8 byte-order marker
|
||||||
entry: pre-commit-hooks-removed check-byte-order-marker fix-byte-order-marker https://github.com/pre-commit/pre-commit-hooks
|
entry: check-byte-order-marker
|
||||||
language: python
|
language: python
|
||||||
types: [text]
|
types: [text]
|
||||||
- id: check-builtin-literals
|
- id: check-builtin-literals
|
||||||
name: check builtin type constructor use
|
name: Check builtin type constructor use
|
||||||
description: requires literal syntax when initializing empty or zero python builtin types.
|
description: Require literal syntax when initializing empty or zero Python builtin types.
|
||||||
entry: check-builtin-literals
|
entry: check-builtin-literals
|
||||||
language: python
|
language: python
|
||||||
types: [python]
|
types: [python]
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
name: check for case conflicts
|
name: Check for case conflicts
|
||||||
description: checks for files that would conflict in case-insensitive filesystems.
|
description: Check for files that would conflict in case-insensitive filesystems
|
||||||
entry: check-case-conflict
|
entry: check-case-conflict
|
||||||
language: python
|
language: python
|
||||||
- id: check-docstring-first
|
- id: check-docstring-first
|
||||||
name: check docstring is first (deprecated)
|
name: Check docstring is first
|
||||||
description: checks a common error of defining a docstring after code.
|
description: Checks a common error of defining a docstring after code.
|
||||||
entry: check-docstring-first
|
entry: check-docstring-first
|
||||||
language: python
|
language: python
|
||||||
types: [python]
|
types: [python]
|
||||||
- id: check-executables-have-shebangs
|
- id: check-executables-have-shebangs
|
||||||
name: check that executables have shebangs
|
name: Check that executables have shebangs
|
||||||
description: ensures that (non-binary) executables have a shebang.
|
description: Ensures that (non-binary) executables have a shebang.
|
||||||
entry: check-executables-have-shebangs
|
entry: check-executables-have-shebangs
|
||||||
language: python
|
language: python
|
||||||
types: [text, executable]
|
types: [text, executable]
|
||||||
stages: [pre-commit, pre-push, manual]
|
stages: [commit, push, manual]
|
||||||
minimum_pre_commit_version: 3.2.0
|
|
||||||
- id: check-illegal-windows-names
|
|
||||||
name: check illegal windows names
|
|
||||||
entry: Illegal Windows filenames detected
|
|
||||||
language: fail
|
|
||||||
files: '(?i)((^|/)(CON|PRN|AUX|NUL|COM[\d¹²³]|LPT[\d¹²³])(\.|/|$)|[<>:\"\\|?*\x00-\x1F]|/[^/]*[\.\s]/|[^/]*[\.\s]$)'
|
|
||||||
- id: check-json
|
- id: check-json
|
||||||
name: check json
|
name: Check JSON
|
||||||
description: checks json files for parseable syntax.
|
description: This hook checks json files for parseable syntax.
|
||||||
entry: check-json
|
entry: check-json
|
||||||
language: python
|
language: python
|
||||||
types: [json]
|
types: [json]
|
||||||
- id: check-shebang-scripts-are-executable
|
|
||||||
name: check that scripts with shebangs are executable
|
|
||||||
description: ensures that (non-binary) files with a shebang are executable.
|
|
||||||
entry: check-shebang-scripts-are-executable
|
|
||||||
language: python
|
|
||||||
types: [text]
|
|
||||||
stages: [pre-commit, pre-push, manual]
|
|
||||||
minimum_pre_commit_version: 3.2.0
|
|
||||||
- id: pretty-format-json
|
- id: pretty-format-json
|
||||||
name: pretty format json
|
name: Pretty format JSON
|
||||||
description: sets a standard for formatting json files.
|
description: This hook sets a standard for formatting JSON files.
|
||||||
entry: pretty-format-json
|
entry: pretty-format-json
|
||||||
language: python
|
language: python
|
||||||
types: [json]
|
types: [json]
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
name: check for merge conflicts
|
name: Check for merge conflicts
|
||||||
description: checks for files that contain merge conflict strings.
|
description: Check for files that contain merge conflict strings.
|
||||||
entry: check-merge-conflict
|
entry: check-merge-conflict
|
||||||
language: python
|
language: python
|
||||||
types: [text]
|
types: [text]
|
||||||
- id: check-symlinks
|
- id: check-symlinks
|
||||||
name: check for broken symlinks
|
name: Check for broken symlinks
|
||||||
description: checks for symlinks which do not point to anything.
|
description: Checks for symlinks which do not point to anything.
|
||||||
entry: check-symlinks
|
entry: check-symlinks
|
||||||
language: python
|
language: python
|
||||||
types: [symlink]
|
types: [symlink]
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
name: check toml
|
name: Check Toml
|
||||||
description: checks toml files for parseable syntax.
|
description: This hook checks toml files for parseable syntax.
|
||||||
entry: check-toml
|
entry: check-toml
|
||||||
language: python
|
language: python
|
||||||
types: [toml]
|
types: [toml]
|
||||||
- id: check-vcs-permalinks
|
- id: check-vcs-permalinks
|
||||||
name: check vcs permalinks
|
name: Check vcs permalinks
|
||||||
description: ensures that links to vcs websites are permalinks.
|
description: Ensures that links to vcs websites are permalinks.
|
||||||
entry: check-vcs-permalinks
|
entry: check-vcs-permalinks
|
||||||
language: python
|
language: python
|
||||||
types: [text]
|
types: [text]
|
||||||
- id: check-xml
|
- id: check-xml
|
||||||
name: check xml
|
name: Check Xml
|
||||||
description: checks xml files for parseable syntax.
|
description: This hook checks xml files for parseable syntax.
|
||||||
entry: check-xml
|
entry: check-xml
|
||||||
language: python
|
language: python
|
||||||
types: [xml]
|
types: [xml]
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
name: check yaml
|
name: Check Yaml
|
||||||
description: checks yaml files for parseable syntax.
|
description: This hook checks yaml files for parseable syntax.
|
||||||
entry: check-yaml
|
entry: check-yaml
|
||||||
language: python
|
language: python
|
||||||
types: [yaml]
|
types: [yaml]
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
name: debug statements (python)
|
name: Debug Statements (Python)
|
||||||
description: checks for debugger imports and py37+ `breakpoint()` calls in python source.
|
description: Check for debugger imports and py37+ `breakpoint()` calls in python source.
|
||||||
entry: debug-statement-hook
|
entry: debug-statement-hook
|
||||||
language: python
|
language: python
|
||||||
types: [python]
|
types: [python]
|
||||||
- id: destroyed-symlinks
|
|
||||||
name: detect destroyed symlinks
|
|
||||||
description: detects symlinks which are changed to regular files with a content of a path which that symlink was pointing to.
|
|
||||||
entry: destroyed-symlinks
|
|
||||||
language: python
|
|
||||||
types: [file]
|
|
||||||
stages: [pre-commit, pre-push, manual]
|
|
||||||
- id: detect-aws-credentials
|
- id: detect-aws-credentials
|
||||||
name: detect aws credentials
|
name: Detect AWS Credentials
|
||||||
description: detects *your* aws credentials from the aws cli credentials file.
|
description: Detects *your* aws credentials from the aws cli credentials file
|
||||||
entry: detect-aws-credentials
|
entry: detect-aws-credentials
|
||||||
language: python
|
language: python
|
||||||
types: [text]
|
types: [text]
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
name: detect private key
|
name: Detect Private Key
|
||||||
description: detects the presence of private keys.
|
description: Detects the presence of private keys
|
||||||
entry: detect-private-key
|
entry: detect-private-key
|
||||||
language: python
|
language: python
|
||||||
types: [text]
|
types: [text]
|
||||||
- id: double-quote-string-fixer
|
- id: double-quote-string-fixer
|
||||||
name: fix double quoted strings
|
name: Fix double quoted strings
|
||||||
description: replaces double quoted strings with single quoted strings.
|
description: This hook replaces double quoted strings with single quoted strings
|
||||||
entry: double-quote-string-fixer
|
entry: double-quote-string-fixer
|
||||||
language: python
|
language: python
|
||||||
types: [python]
|
types: [python]
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
name: fix end of files
|
name: Fix End of Files
|
||||||
description: ensures that a file is either empty, or ends with one newline.
|
description: Ensures that a file is either empty, or ends with one newline.
|
||||||
entry: end-of-file-fixer
|
entry: end-of-file-fixer
|
||||||
language: python
|
language: python
|
||||||
types: [text]
|
types: [text]
|
||||||
stages: [pre-commit, pre-push, manual]
|
stages: [commit, push, manual]
|
||||||
minimum_pre_commit_version: 3.2.0
|
|
||||||
- id: file-contents-sorter
|
- id: file-contents-sorter
|
||||||
name: file contents sorter
|
name: File Contents Sorter
|
||||||
description: sorts the lines in specified files (defaults to alphabetical). you must provide list of target files as input in your .pre-commit-config.yaml file.
|
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
|
entry: file-contents-sorter
|
||||||
language: python
|
language: python
|
||||||
files: '^$'
|
files: '^$'
|
||||||
- id: fix-byte-order-marker
|
|
||||||
name: fix utf-8 byte order marker
|
|
||||||
description: removes utf-8 byte order marker.
|
|
||||||
entry: fix-byte-order-marker
|
|
||||||
language: python
|
|
||||||
types: [text]
|
|
||||||
- id: fix-encoding-pragma
|
- id: fix-encoding-pragma
|
||||||
name: fix python encoding pragma (removed)
|
name: Fix python encoding pragma
|
||||||
description: (removed) use pyupgrade instead.
|
language: python
|
||||||
entry: pre-commit-hooks-removed fix-encoding-pragma pyupgrade https://github.com/asottile/pyupgrade
|
entry: fix-encoding-pragma
|
||||||
|
description: 'Add # -*- coding: utf-8 -*- to the top of python files'
|
||||||
|
types: [python]
|
||||||
|
- id: flake8
|
||||||
|
name: Flake8 (deprecated, use gitlab.com/pycqa/flake8)
|
||||||
|
description: This hook runs flake8.
|
||||||
|
entry: flake8
|
||||||
language: python
|
language: python
|
||||||
types: [python]
|
types: [python]
|
||||||
|
require_serial: true
|
||||||
- id: forbid-new-submodules
|
- id: forbid-new-submodules
|
||||||
name: forbid new submodules
|
name: Forbid new submodules
|
||||||
description: prevents addition of new git submodules.
|
|
||||||
language: python
|
language: python
|
||||||
entry: forbid-new-submodules
|
entry: forbid-new-submodules
|
||||||
types: [directory]
|
description: Prevent addition of new git submodules
|
||||||
- id: forbid-submodules
|
|
||||||
name: forbid submodules
|
|
||||||
description: forbids any submodules in the repository
|
|
||||||
language: fail
|
|
||||||
entry: 'submodules are not allowed in this repository:'
|
|
||||||
types: [directory]
|
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
name: mixed line ending
|
name: Mixed line ending
|
||||||
description: replaces or checks mixed line ending.
|
description: Replaces or checks mixed line ending
|
||||||
entry: mixed-line-ending
|
entry: mixed-line-ending
|
||||||
language: python
|
language: python
|
||||||
types: [text]
|
types: [text]
|
||||||
- id: name-tests-test
|
- id: name-tests-test
|
||||||
name: python tests naming
|
name: Tests should end in _test.py
|
||||||
description: verifies that test files are named correctly.
|
description: This verifies that test files are named correctly
|
||||||
entry: name-tests-test
|
entry: name-tests-test
|
||||||
language: python
|
language: python
|
||||||
files: (^|/)tests/.+\.py$
|
files: (^|/)tests/.+\.py$
|
||||||
- id: no-commit-to-branch
|
- id: no-commit-to-branch
|
||||||
name: "don't commit to branch"
|
name: "Don't commit to branch"
|
||||||
entry: no-commit-to-branch
|
entry: no-commit-to-branch
|
||||||
language: python
|
language: python
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
always_run: true
|
always_run: true
|
||||||
|
- id: pyflakes
|
||||||
|
name: Pyflakes (DEPRECATED, use flake8)
|
||||||
|
description: This hook runs pyflakes. (This is deprecated, use flake8).
|
||||||
|
entry: pyflakes
|
||||||
|
language: python
|
||||||
|
types: [python]
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
name: fix requirements.txt
|
name: Fix requirements.txt
|
||||||
description: sorts entries in requirements.txt.
|
description: Sorts entries in requirements.txt
|
||||||
entry: requirements-txt-fixer
|
entry: requirements-txt-fixer
|
||||||
language: python
|
language: python
|
||||||
files: (requirements|constraints).*\.txt$
|
files: requirements.*\.txt$
|
||||||
- id: sort-simple-yaml
|
- id: sort-simple-yaml
|
||||||
name: sort simple yaml files
|
name: Sort simple YAML files
|
||||||
description: sorts simple yaml files which consist only of top-level keys, preserving comments and blocks.
|
|
||||||
language: python
|
language: python
|
||||||
entry: sort-simple-yaml
|
entry: sort-simple-yaml
|
||||||
|
description: Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks.
|
||||||
files: '^$'
|
files: '^$'
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
name: trim trailing whitespace
|
name: Trim Trailing Whitespace
|
||||||
description: trims trailing whitespace.
|
description: This hook trims trailing whitespace.
|
||||||
entry: trailing-whitespace-fixer
|
entry: trailing-whitespace-fixer
|
||||||
language: python
|
language: python
|
||||||
types: [text]
|
types: [text]
|
||||||
stages: [pre-commit, pre-push, manual]
|
stages: [commit, push, manual]
|
||||||
minimum_pre_commit_version: 3.2.0
|
|
||||||
|
|
|
||||||
319
CHANGELOG.md
319
CHANGELOG.md
|
|
@ -1,320 +1,3 @@
|
||||||
6.0.0 - 2025-08-09
|
|
||||||
==================
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
- `check-shebang-scripts-are-executable`: improve error message.
|
|
||||||
- #1115 PR by @homebysix.
|
|
||||||
|
|
||||||
## Migrating
|
|
||||||
- now requires python >= 3.9.
|
|
||||||
- #1098 PR by @asottile.
|
|
||||||
- `file-contents-sorter`: disallow `--unique` and `--ignore-case` at the same
|
|
||||||
time.
|
|
||||||
- #1095 PR by @nemacysts.
|
|
||||||
- #794 issue by @teksturi.
|
|
||||||
- Removed `check-byte-order-marker` and `fix-encoding-pragma`.
|
|
||||||
- `check-byte-order-marker`: migrate to `fix-byte-order-marker`.
|
|
||||||
- `fix-encoding-pragma`: migrate to `pyupgrade`.
|
|
||||||
- #1034 PR by @mxr.
|
|
||||||
- #1032 issue by @mxr.
|
|
||||||
- #522 PR by @jgowdy.
|
|
||||||
|
|
||||||
5.0.0 - 2024-10-05
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `requirements-txt-fixer`: also remove `pkg_resources==...`.
|
|
||||||
- #850 PR by @ericfrederich.
|
|
||||||
- #1030 issue by @ericfrederich.
|
|
||||||
- `check-illegal-windows-names`: new hook!
|
|
||||||
- #1044 PR by @ericfrederich.
|
|
||||||
- #589 issue by @ericfrederich.
|
|
||||||
- #1049 PR by @Jeffrey-Lim.
|
|
||||||
- `pretty-format-json`: continue processing even if a file has a json error.
|
|
||||||
- #1039 PR by @amarvin.
|
|
||||||
- #1038 issue by @amarvin.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `destroyed-symlinks`: set `stages` to `[pre-commit, pre-push, manual]`
|
|
||||||
- PR #1085 by @AdrianDC.
|
|
||||||
|
|
||||||
### Migrating
|
|
||||||
- pre-commit-hooks now requires `pre-commit>=3.2.0`.
|
|
||||||
- use non-deprecated names for `stages`.
|
|
||||||
- #1093 PR by @asottile.
|
|
||||||
|
|
||||||
4.6.0 - 2024-04-06
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `requirements-txt-fixer`: remove duplicate packages.
|
|
||||||
- #1014 PR by @vhoulbreque-withings.
|
|
||||||
- #960 issue @csibe17.
|
|
||||||
|
|
||||||
### Migrating
|
|
||||||
- `fix-encoding-pragma`: deprecated -- will be removed in 5.0.0. use
|
|
||||||
[pyupgrade](https://github.com/asottile/pyupgrade) or some other tool.
|
|
||||||
- #1033 PR by @mxr.
|
|
||||||
- #1032 issue by @mxr.
|
|
||||||
|
|
||||||
4.5.0 - 2023-10-07
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `requirements-txt-fixer`: also sort `constraints.txt` by default.
|
|
||||||
- #857 PR by @lev-blit.
|
|
||||||
- #830 issue by @PLPeeters.
|
|
||||||
- `debug-statements`: add `bpdb` debugger.
|
|
||||||
- #942 PR by @mwip.
|
|
||||||
- #941 issue by @mwip.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `file-contents-sorter`: fix sorting an empty file.
|
|
||||||
- #944 PR by @RoelAdriaans.
|
|
||||||
- #935 issue by @paduszyk.
|
|
||||||
- `double-quote-string-fixer`: don't rewrite inside f-strings in 3.12+.
|
|
||||||
- #973 PR by @asottile.
|
|
||||||
- #971 issue by @XuehaiPan.
|
|
||||||
|
|
||||||
## Migrating
|
|
||||||
- now requires python >= 3.8.
|
|
||||||
- #926 PR by @asottile.
|
|
||||||
- #927 PR by @asottile.
|
|
||||||
|
|
||||||
4.4.0 - 2022-11-23
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `forbid-submodules`: new hook which outright bans submodules.
|
|
||||||
- #815 PR by @asottile.
|
|
||||||
- #707 issue by @ChiefGokhlayeh.
|
|
||||||
|
|
||||||
4.3.0 - 2022-06-07
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `check-executables-have-shebangs`: use `git config core.fileMode` to
|
|
||||||
determine if it should query `git`.
|
|
||||||
- #730 PR by @Kurt-von-Laven.
|
|
||||||
- `name-tests-test`: add `--pytest-test-first` test convention.
|
|
||||||
- #779 PR by @asottile.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `check-shebang-scripts-are-executable`: update windows instructions.
|
|
||||||
- #774 PR by @mdeweerd.
|
|
||||||
- #770 issue by @mdeweerd.
|
|
||||||
- `check-toml`: use stdlib `tomllib` when available.
|
|
||||||
- #771 PR by @DanielNoord.
|
|
||||||
- #755 issue by @sognetic.
|
|
||||||
- `check-added-large-files`: don't run on non-file `stages`.
|
|
||||||
- #778 PR by @asottile.
|
|
||||||
- #777 issue by @skyj.
|
|
||||||
|
|
||||||
4.2.0 - 2022-04-06
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `name-tests-test`: updated display text.
|
|
||||||
- #713 PR by @asottile.
|
|
||||||
- `check-docstring-first`: make output more parsable.
|
|
||||||
- #748 PR by @asottile.
|
|
||||||
- `check-merge-conflict`: make output more parsable.
|
|
||||||
- #748 PR by @asottile.
|
|
||||||
- `debug-statements`: make output more parsable.
|
|
||||||
- #748 PR by @asottile.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `check-merge-conflict`: fix detection of `======` conflict marker on windows.
|
|
||||||
- #748 PR by @asottile.
|
|
||||||
|
|
||||||
### Updating
|
|
||||||
- Drop python<3.7.
|
|
||||||
- #719 PR by @asottile.
|
|
||||||
- Changed default branch from `master` to `main`.
|
|
||||||
- #744 PR by @asottile.
|
|
||||||
|
|
||||||
4.1.0 - 2021-12-22
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `debug-statements`: add `pdbr` debugger.
|
|
||||||
- #614 PR by @cansarigol.
|
|
||||||
- `detect-private-key`: add detection for additional key types.
|
|
||||||
- #658 PR by @ljmf00.
|
|
||||||
- `check-executables-have-shebangs`: improve messaging on windows.
|
|
||||||
- #689 PR by @pujitm.
|
|
||||||
- #686 issue by @jmerdich.
|
|
||||||
- `check-added-large-files`: support `--enforce-all` with `git-lfs`.
|
|
||||||
- #674 PR by @amartani.
|
|
||||||
- #560 issue by @jeremy-coulon.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `check-case-conflict`: improve performance.
|
|
||||||
- #626 PR by @guykisel.
|
|
||||||
- #625 issue by @guykisel.
|
|
||||||
- `forbid-new-submodules`: fix false-negatives for `pre-push`.
|
|
||||||
- #619 PR by @m-khvoinitsky.
|
|
||||||
- #609 issue by @m-khvoinitsky.
|
|
||||||
- `check-merge-conflict`: fix execution in git worktrees.
|
|
||||||
- #662 PR by @errsyn.
|
|
||||||
- #638 issue by @daschuer.
|
|
||||||
|
|
||||||
### Misc.
|
|
||||||
- Normalize case of hook names and descriptions.
|
|
||||||
- #671 PR by @dennisroche.
|
|
||||||
- #673 PR by @revolter.
|
|
||||||
|
|
||||||
4.0.1 - 2021-05-16
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `check-shebang-scripts-are-executable` fix entry point.
|
|
||||||
- #602 issue by @Person-93.
|
|
||||||
- #603 PR by @scop.
|
|
||||||
|
|
||||||
4.0.0 - 2021-05-14
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `check-json`: report duplicate keys.
|
|
||||||
- #558 PR by @AdityaKhursale.
|
|
||||||
- #554 issue by @adamchainz.
|
|
||||||
- `no-commit-to-branch`: add `main` to default blocked branches.
|
|
||||||
- #565 PR by @ndevenish.
|
|
||||||
- `check-case-conflict`: check conflicts in directory names as well.
|
|
||||||
- #575 PR by @slsyy.
|
|
||||||
- #70 issue by @andyjack.
|
|
||||||
- `check-vcs-permalinks`: forbid other branch names.
|
|
||||||
- #582 PR by @jack1142.
|
|
||||||
- #581 issue by @jack1142.
|
|
||||||
- `check-shebang-scripts-are-executable`: new hook which ensures shebang'd
|
|
||||||
scripts are executable.
|
|
||||||
- #545 PR by @scop.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `check-executables-have-shebangs`: Short circuit shebang lookup on windows.
|
|
||||||
- #544 PR by @scop.
|
|
||||||
- `requirements-txt-fixer`: Fix comments which have indentation
|
|
||||||
- #549 PR by @greshilov.
|
|
||||||
- #548 issue by @greshilov.
|
|
||||||
- `pretty-format-json`: write to stdout using UTF-8 encoding.
|
|
||||||
- #571 PR by @jack1142.
|
|
||||||
- #570 issue by @jack1142.
|
|
||||||
- Use more inclusive language.
|
|
||||||
- #599 PR by @asottile.
|
|
||||||
|
|
||||||
### Breaking changes
|
|
||||||
- Remove deprecated hooks: `flake8`, `pyflakes`, `autopep8-wrapper`.
|
|
||||||
- #597 PR by @asottile.
|
|
||||||
|
|
||||||
|
|
||||||
3.4.0 - 2020-12-15
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `file-contents-sorter`: Add `--unique` argument
|
|
||||||
- #524 PR by @danielhoherd.
|
|
||||||
- `check-vcs-permalinks`: Add `--additional-github-domain` option
|
|
||||||
- #530 PR by @youngminz.
|
|
||||||
- New hook: `destroyed-symlinks` to detect unintentional symlink-breakages on
|
|
||||||
windows.
|
|
||||||
- #511 PR by @m-khvoinitsky.
|
|
||||||
|
|
||||||
3.3.0 - 2020-10-20
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `file-contents-sorter`: add `--ignore-case` option for case-insensitive
|
|
||||||
sorting
|
|
||||||
- #514 PR by @Julian.
|
|
||||||
- `check-added-large-files`: add `--enforce-all` option to check non-added
|
|
||||||
files as well
|
|
||||||
- #519 PR by @mshawcroft.
|
|
||||||
- #518 issue by @mshawcroft.
|
|
||||||
- `fix-byte-order-marker`: new hook which fixes UTF-8 byte-order marker.
|
|
||||||
- #522 PR by @jgowdy.
|
|
||||||
|
|
||||||
### Deprecations
|
|
||||||
- `check-byte-order-marker` is now deprecated for `fix-byte-order-marker`
|
|
||||||
|
|
||||||
3.2.0 - 2020-07-30
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `debug-statements`: add support for `pydevd_pycharm` debugger
|
|
||||||
- #502 PR by @jgeerds.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `check-executables-have-shebangs`: fix git-quoted files on windows (spaces,
|
|
||||||
non-ascii, etc.)
|
|
||||||
- #509 PR by @pawamoy.
|
|
||||||
- #508 issue by @pawamoy.
|
|
||||||
|
|
||||||
3.1.0 - 2020-05-20
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `check-executables-have-shebangs`: on windows, validate the mode bits using
|
|
||||||
`git`
|
|
||||||
- #480 PR by @mxr.
|
|
||||||
- #435 issue by @dstandish.
|
|
||||||
- `requirements-txt-fixer`: support more operators
|
|
||||||
- #483 PR by @mxr.
|
|
||||||
- #331 issue by @hackedd.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `pre-commit-hooks-removed`: Fix when removed hooks used `args`
|
|
||||||
- #487 PR by @pedrocalleja.
|
|
||||||
- #485 issue by @pedrocalleja.
|
|
||||||
|
|
||||||
3.0.1 - 2020-05-16
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `check-toml`: use UTF-8 encoding to load toml files
|
|
||||||
- #479 PR by @mxr.
|
|
||||||
- #474 issue by @staticdev.
|
|
||||||
|
|
||||||
3.0.0 - 2020-05-14
|
|
||||||
==================
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- `detect-aws-credentials`: skip empty aws keys
|
|
||||||
- #450 PR by @begoon.
|
|
||||||
- #449 issue by @begoon.
|
|
||||||
- `debug-statements`: add detection `wdb` debugger
|
|
||||||
- #452 PR by @itsdkey.
|
|
||||||
- #451 issue by @itsdkey.
|
|
||||||
- `requirements-txt-fixer`: support line continuation for dependencies
|
|
||||||
- #469 PR by @aniketbhatnagar.
|
|
||||||
- #465 issue by @aniketbhatnagar.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- `detect-aws-credentials`: fix `UnicodeDecodeError` when running on non-UTF8
|
|
||||||
files.
|
|
||||||
- #453 PR by @asottile.
|
|
||||||
- #393 PR by @a7p
|
|
||||||
- #346 issue by @rpdelaney.
|
|
||||||
|
|
||||||
### Updating
|
|
||||||
- pre-commit/pre-commit-hooks now requires python3.6.1+
|
|
||||||
- #447 PR by @asottile.
|
|
||||||
- #455 PR by @asottile.
|
|
||||||
- `flake8` / `pyflakes` have been removed, use `flake8` from `pycqa/flake8`
|
|
||||||
instead:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
|
||||||
rev: 3.8.1
|
|
||||||
hooks:
|
|
||||||
- id: flake8
|
|
||||||
```
|
|
||||||
|
|
||||||
- #476 PR by @asottile.
|
|
||||||
- #477 PR by @asottile.
|
|
||||||
- #344 issue by @asottile.
|
|
||||||
|
|
||||||
|
|
||||||
2.5.0 - 2020-02-04
|
2.5.0 - 2020-02-04
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
|
@ -322,7 +5,7 @@
|
||||||
- Fix sorting of requirements which use `egg=...`
|
- Fix sorting of requirements which use `egg=...`
|
||||||
- #425 PR by @vinayinvicible.
|
- #425 PR by @vinayinvicible.
|
||||||
- Fix over-eager regular expression for test filename matching
|
- Fix over-eager regular expression for test filename matching
|
||||||
- #429 PR by @rrauenza.
|
- #429 PR by rrauenza.
|
||||||
|
|
||||||
### Updating
|
### Updating
|
||||||
- Use `flake8` from `pycqa/flake8` instead:
|
- Use `flake8` from `pycqa/flake8` instead:
|
||||||
|
|
|
||||||
88
README.md
88
README.md
|
|
@ -1,5 +1,5 @@
|
||||||
[](https://github.com/pre-commit/pre-commit-hooks/actions/workflows/main.yml)
|
[](https://asottile.visualstudio.com/asottile/_build/latest?definitionId=17&branchName=master)
|
||||||
[](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit-hooks/main)
|
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=17&branchName=master)
|
||||||
|
|
||||||
pre-commit-hooks
|
pre-commit-hooks
|
||||||
================
|
================
|
||||||
|
|
@ -13,24 +13,20 @@ See also: https://github.com/pre-commit/pre-commit
|
||||||
|
|
||||||
Add this to your `.pre-commit-config.yaml`
|
Add this to your `.pre-commit-config.yaml`
|
||||||
|
|
||||||
```yaml
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
rev: v2.5.0 # Use the ref you want to point at
|
||||||
rev: v6.0.0 # Use the ref you want to point at
|
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
# - id: ...
|
# - id: ...
|
||||||
```
|
|
||||||
|
|
||||||
### Hooks available
|
### Hooks available
|
||||||
|
|
||||||
#### `check-added-large-files`
|
#### `check-added-large-files`
|
||||||
Prevent giant files from being committed.
|
Prevent giant files from being committed.
|
||||||
- Specify what is "too large" with `args: ['--maxkb=123']` (default=500kB).
|
- Specify what is "too large" with `args: ['--maxkb=123']` (default=500kB).
|
||||||
- Limits checked files to those indicated as staged for addition by git.
|
|
||||||
- If `git-lfs` is installed, lfs files will be skipped
|
- If `git-lfs` is installed, lfs files will be skipped
|
||||||
(requires `git-lfs>=2.2.1`)
|
(requires `git-lfs>=2.2.1`)
|
||||||
- `--enforce-all` - Check all listed files not just those staged for
|
|
||||||
addition.
|
|
||||||
|
|
||||||
#### `check-ast`
|
#### `check-ast`
|
||||||
Simply check whether files parse as valid python.
|
Simply check whether files parse as valid python.
|
||||||
|
|
@ -42,24 +38,23 @@ Require literal syntax when initializing empty or zero Python builtin types.
|
||||||
- Ignore this requirement for specific builtin types with `--ignore=type1,type2,…`.
|
- Ignore this requirement for specific builtin types with `--ignore=type1,type2,…`.
|
||||||
- Forbid `dict` keyword syntax with `--no-allow-dict-kwargs`.
|
- Forbid `dict` keyword syntax with `--no-allow-dict-kwargs`.
|
||||||
|
|
||||||
|
#### `check-byte-order-marker`
|
||||||
|
Forbid files which have a UTF-8 byte-order marker
|
||||||
|
|
||||||
#### `check-case-conflict`
|
#### `check-case-conflict`
|
||||||
Check for files with names that would conflict on a case-insensitive filesystem like MacOS HFS+ or Windows FAT.
|
Check for files with names that would conflict on a 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`
|
#### `check-executables-have-shebangs`
|
||||||
Checks that non-binary executables have a proper shebang.
|
Checks that non-binary executables have a proper shebang.
|
||||||
|
|
||||||
#### `check-illegal-windows-names`
|
|
||||||
Check for files that cannot be created on Windows.
|
|
||||||
|
|
||||||
#### `check-json`
|
#### `check-json`
|
||||||
Attempts to load all json files to verify syntax.
|
Attempts to load all json files to verify syntax.
|
||||||
|
|
||||||
#### `check-merge-conflict`
|
#### `check-merge-conflict`
|
||||||
Check for files that contain merge conflict strings.
|
Check for files that contain merge conflict strings.
|
||||||
- `--assume-in-merge` - Allows running the hook when there is no ongoing merge operation
|
|
||||||
|
|
||||||
#### `check-shebang-scripts-are-executable`
|
|
||||||
Checks that scripts with shebangs are executable.
|
|
||||||
|
|
||||||
#### `check-symlinks`
|
#### `check-symlinks`
|
||||||
Checks for symlinks which do not point to anything.
|
Checks for symlinks which do not point to anything.
|
||||||
|
|
@ -69,10 +64,6 @@ Attempts to load all TOML files to verify syntax.
|
||||||
|
|
||||||
#### `check-vcs-permalinks`
|
#### `check-vcs-permalinks`
|
||||||
Ensures that links to vcs websites are permalinks.
|
Ensures that links to vcs websites are permalinks.
|
||||||
- `--additional-github-domain DOMAIN` - Add check for specified domain.
|
|
||||||
Can be repeated multiple times. for example, if your company uses
|
|
||||||
GitHub Enterprise you may use something like
|
|
||||||
`--additional-github-domain github.example.com`
|
|
||||||
|
|
||||||
#### `check-xml`
|
#### `check-xml`
|
||||||
Attempts to load all xml files to verify syntax.
|
Attempts to load all xml files to verify syntax.
|
||||||
|
|
@ -90,12 +81,6 @@ Attempts to load all yaml files to verify syntax.
|
||||||
#### `debug-statements`
|
#### `debug-statements`
|
||||||
Check for debugger imports and py37+ `breakpoint()` calls in python source.
|
Check for debugger imports and py37+ `breakpoint()` calls in python source.
|
||||||
|
|
||||||
#### `destroyed-symlinks`
|
|
||||||
Detects symlinks which are changed to regular files with a content of a path
|
|
||||||
which that symlink was pointing to.
|
|
||||||
This usually happens on Windows when a user clones a repository that has
|
|
||||||
symlinks but they do not have the permission to create symlinks.
|
|
||||||
|
|
||||||
#### `detect-aws-credentials`
|
#### `detect-aws-credentials`
|
||||||
Checks for the existence of AWS secrets that you have set up with the AWS CLI.
|
Checks for the existence of AWS secrets that you have set up with the AWS CLI.
|
||||||
The following arguments are available:
|
The following arguments are available:
|
||||||
|
|
@ -113,28 +98,18 @@ This hook replaces double quoted strings with single quoted strings.
|
||||||
#### `end-of-file-fixer`
|
#### `end-of-file-fixer`
|
||||||
Makes sure files end in a newline and only a newline.
|
Makes sure files end in a newline and only a newline.
|
||||||
|
|
||||||
|
#### `fix-encoding-pragma`
|
||||||
|
Add `# -*- coding: utf-8 -*-` to the top of python files.
|
||||||
|
- To remove the coding pragma pass `--remove` (useful in a python3-only codebase)
|
||||||
|
|
||||||
#### `file-contents-sorter`
|
#### `file-contents-sorter`
|
||||||
Sort the lines in specified files (defaults to alphabetical).
|
Sort the lines in specified files (defaults to alphabetical).
|
||||||
You must provide the target [`files`](https://pre-commit.com/#config-files) as input.
|
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.
|
Note that this hook WILL remove blank lines and does NOT respect any comments.
|
||||||
All newlines will be converted to line feeds (`\n`).
|
|
||||||
|
|
||||||
The following arguments are available:
|
|
||||||
- `--ignore-case` - fold lower case to upper case characters.
|
|
||||||
- `--unique` - ensure each line is unique.
|
|
||||||
|
|
||||||
#### `fix-byte-order-marker`
|
|
||||||
removes UTF-8 byte order marker
|
|
||||||
|
|
||||||
#### `forbid-new-submodules`
|
#### `forbid-new-submodules`
|
||||||
Prevent addition of new git submodules.
|
Prevent addition of new git submodules.
|
||||||
|
|
||||||
This is intended as a helper to migrate away from submodules. If you want to
|
|
||||||
ban them entirely use `forbid-submodules`
|
|
||||||
|
|
||||||
#### `forbid-submodules`
|
|
||||||
forbids any submodules in the repository.
|
|
||||||
|
|
||||||
#### `mixed-line-ending`
|
#### `mixed-line-ending`
|
||||||
Replaces or checks mixed line ending.
|
Replaces or checks mixed line ending.
|
||||||
- `--fix={auto,crlf,lf,no}`
|
- `--fix={auto,crlf,lf,no}`
|
||||||
|
|
@ -144,46 +119,37 @@ Replaces or checks mixed line ending.
|
||||||
- `no` - Checks if there is any mixed line ending without modifying any file.
|
- `no` - Checks if there is any mixed line ending without modifying any file.
|
||||||
|
|
||||||
#### `name-tests-test`
|
#### `name-tests-test`
|
||||||
verifies that test files are named correctly.
|
Assert that files in tests/ end in `_test.py`.
|
||||||
- `--pytest` (the default): ensure tests match `.*_test\.py`
|
- Use `args: ['--django']` to match `test*.py` instead.
|
||||||
- `--pytest-test-first`: ensure tests match `test_.*\.py`
|
|
||||||
- `--django` / `--unittest`: ensure tests match `test.*\.py`
|
|
||||||
|
|
||||||
#### `no-commit-to-branch`
|
#### `no-commit-to-branch`
|
||||||
Protect specific branches from direct checkins.
|
Protect specific branches from direct checkins.
|
||||||
- Use `args: [--branch, staging, --branch, main]` to set the branch.
|
- Use `args: [--branch, staging, --branch, master]` to set the branch.
|
||||||
Both `main` and `master` are protected by default if no branch argument is set.
|
`master` is the default if no branch argument is set.
|
||||||
- `-b` / `--branch` may be specified multiple times to protect multiple
|
- `-b` / `--branch` may be specified multiple times to protect multiple
|
||||||
branches.
|
branches.
|
||||||
- `-p` / `--pattern` can be used to protect branches that match a supplied regex
|
- `-p` / `--pattern` can be used to protect branches that match a supplied regex
|
||||||
(e.g. `--pattern, release/.*`). May be specified multiple times.
|
(e.g. `--pattern, release/.*`). May be specified multiple times.
|
||||||
|
|
||||||
Note that `no-commit-to-branch` is configured by default to [`always_run`](https://pre-commit.com/#config-always_run).
|
|
||||||
As a result, it will ignore any setting of [`files`](https://pre-commit.com/#config-files),
|
|
||||||
[`exclude`](https://pre-commit.com/#config-exclude), [`types`](https://pre-commit.com/#config-types)
|
|
||||||
or [`exclude_types`](https://pre-commit.com/#config-exclude_types).
|
|
||||||
Set [`always_run: false`](https://pre-commit.com/#config-always_run) to allow this hook to be skipped according to these
|
|
||||||
file filters. Caveat: In this configuration, empty commits (`git commit --allow-empty`) would always be allowed by this hook.
|
|
||||||
|
|
||||||
#### `pretty-format-json`
|
#### `pretty-format-json`
|
||||||
Checks that all your JSON files are pretty. "Pretty"
|
Checks that all your JSON files are pretty. "Pretty"
|
||||||
here means that keys are sorted and indented. You can configure this with
|
here means that keys are sorted and indented. You can configure this with
|
||||||
the following commandline options:
|
the following commandline options:
|
||||||
- `--autofix` - automatically format json files
|
- `--autofix` - automatically format json files
|
||||||
- `--indent ...` - Control the indentation (either a number for a number of spaces or a string of whitespace). Defaults to 2 spaces.
|
- `--indent ...` - Control the indentation (either a number for a number of spaces or a string of whitespace). Defaults to 4 spaces.
|
||||||
- `--no-ensure-ascii` preserve unicode characters instead of converting to escape sequences
|
- `--no-ensure-ascii` preserve unicode characters instead of converting to escape sequences
|
||||||
- `--no-sort-keys` - when autofixing, retain the original key ordering (instead of sorting the keys)
|
- `--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.
|
- `--top-keys comma,separated,keys` - Keys to keep at the top of mappings.
|
||||||
|
|
||||||
#### `requirements-txt-fixer`
|
#### `requirements-txt-fixer`
|
||||||
Sorts entries in requirements.txt and constraints.txt and removes incorrect entry for `pkg-resources==0.0.0`
|
Sorts entries in requirements.txt and removes incorrect entry for `pkg-resources==0.0.0`
|
||||||
|
|
||||||
#### `sort-simple-yaml`
|
#### `sort-simple-yaml`
|
||||||
Sorts simple YAML files which consist only of top-level
|
Sorts simple YAML files which consist only of top-level
|
||||||
keys, preserving comments and blocks.
|
keys, preserving comments and blocks.
|
||||||
|
|
||||||
Note that `sort-simple-yaml` by default matches no `files` as it enforces a
|
Note that `sort-simple-yaml` by default matches no `files` as it enforces a
|
||||||
very specific format. You must opt in to this by setting [`files`](https://pre-commit.com/#config-files), for example:
|
very specific format. You must opt in to this by setting `files`, for example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- id: sort-simple-yaml
|
- id: sort-simple-yaml
|
||||||
|
|
@ -202,10 +168,10 @@ Trims trailing whitespace.
|
||||||
|
|
||||||
### Deprecated / replaced hooks
|
### Deprecated / replaced hooks
|
||||||
|
|
||||||
- `check-byte-order-marker`: instead use fix-byte-order-marker
|
- `autopep8-wrapper`: instead use
|
||||||
- `fix-encoding-pragma`: instead use [`pyupgrade`](https://github.com/asottile/pyupgrade)
|
[mirrors-autopep8](https://github.com/pre-commit/mirrors-autopep8)
|
||||||
- `check-docstring-first`: fundamentally flawed, deprecated without replacement.
|
- `pyflakes`: instead use `flake8`
|
||||||
|
- `flake8`: instead use [upstream flake8](https://gitlab.com/pycqa/flake8)
|
||||||
|
|
||||||
### As a standalone package
|
### As a standalone package
|
||||||
|
|
||||||
|
|
|
||||||
24
azure-pipelines.yml
Normal file
24
azure-pipelines.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
trigger:
|
||||||
|
branches:
|
||||||
|
include: [master, test-me-*]
|
||||||
|
tags:
|
||||||
|
include: ['*']
|
||||||
|
|
||||||
|
resources:
|
||||||
|
repositories:
|
||||||
|
- repository: asottile
|
||||||
|
type: github
|
||||||
|
endpoint: github
|
||||||
|
name: asottile/azure-pipeline-templates
|
||||||
|
ref: refs/tags/v1.0.0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- template: job--pre-commit.yml@asottile
|
||||||
|
- template: job--python-tox.yml@asottile
|
||||||
|
parameters:
|
||||||
|
toxenvs: [py27, py37]
|
||||||
|
os: windows
|
||||||
|
- template: job--python-tox.yml@asottile
|
||||||
|
parameters:
|
||||||
|
toxenvs: [pypy, pypy3, py27, py36, py37]
|
||||||
|
os: linux
|
||||||
14
pre_commit_hooks/autopep8_wrapper.py
Normal file
14
pre_commit_hooks/autopep8_wrapper.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
|
||||||
|
def main(): # type: () -> int
|
||||||
|
raise SystemExit(
|
||||||
|
'autopep8-wrapper is deprecated. Instead use autopep8 directly via '
|
||||||
|
'https://github.com/pre-commit/mirrors-autopep8',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
exit(main())
|
||||||
|
|
@ -1,81 +1,62 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import subprocess
|
from typing import Iterable
|
||||||
from collections.abc import Sequence
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
from pre_commit_hooks.util import added_files
|
from pre_commit_hooks.util import added_files
|
||||||
from pre_commit_hooks.util import zsplit
|
from pre_commit_hooks.util import CalledProcessError
|
||||||
|
from pre_commit_hooks.util import cmd_output
|
||||||
|
|
||||||
|
|
||||||
def filter_lfs_files(filenames: set[str]) -> None: # pragma: no cover (lfs)
|
def lfs_files(): # type: () -> Set[str]
|
||||||
"""Remove files tracked by git-lfs from the set."""
|
try:
|
||||||
if not filenames:
|
# Introduced in git-lfs 2.2.0, first working in 2.2.1
|
||||||
return
|
lfs_ret = cmd_output('git', 'lfs', 'status', '--json')
|
||||||
|
except CalledProcessError: # pragma: no cover (with git-lfs)
|
||||||
|
lfs_ret = '{"files":{}}'
|
||||||
|
|
||||||
check_attr = subprocess.run(
|
return set(json.loads(lfs_ret)['files'])
|
||||||
('git', 'check-attr', 'filter', '-z', '--stdin'),
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
encoding='utf-8',
|
|
||||||
check=True,
|
|
||||||
input='\0'.join(filenames),
|
|
||||||
)
|
|
||||||
stdout = zsplit(check_attr.stdout)
|
|
||||||
for i in range(0, len(stdout), 3):
|
|
||||||
filename, filter_tag = stdout[i], stdout[i + 2]
|
|
||||||
if filter_tag == 'lfs':
|
|
||||||
filenames.remove(filename)
|
|
||||||
|
|
||||||
|
|
||||||
def find_large_added_files(
|
def find_large_added_files(filenames, maxkb):
|
||||||
filenames: Sequence[str],
|
# type: (Iterable[str], int) -> int
|
||||||
maxkb: int,
|
|
||||||
*,
|
|
||||||
enforce_all: bool = False,
|
|
||||||
) -> int:
|
|
||||||
# Find all added files that are also in the list of files pre-commit tells
|
# Find all added files that are also in the list of files pre-commit tells
|
||||||
# us about
|
# us about
|
||||||
|
filenames = (added_files() & set(filenames)) - lfs_files()
|
||||||
|
|
||||||
retv = 0
|
retv = 0
|
||||||
filenames_filtered = set(filenames)
|
for filename in filenames:
|
||||||
filter_lfs_files(filenames_filtered)
|
kb = int(math.ceil(os.stat(filename).st_size / 1024))
|
||||||
|
|
||||||
if not enforce_all:
|
|
||||||
filenames_filtered &= added_files()
|
|
||||||
|
|
||||||
for filename in filenames_filtered:
|
|
||||||
kb = math.ceil(os.stat(filename).st_size / 1024)
|
|
||||||
if kb > maxkb:
|
if kb > maxkb:
|
||||||
print(f'{filename} ({kb} KB) exceeds {maxkb} KB.')
|
print('{} ({} KB) exceeds {} KB.'.format(filename, kb, maxkb))
|
||||||
retv = 1
|
retv = 1
|
||||||
|
|
||||||
return retv
|
return retv
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'filenames', nargs='*',
|
'filenames', nargs='*',
|
||||||
help='Filenames pre-commit believes are changed.',
|
help='Filenames pre-commit believes are changed.',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
'--enforce-all', action='store_true',
|
|
||||||
help='Enforce all files are checked, not just staged files.',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--maxkb', type=int, default=500,
|
'--maxkb', type=int, default=500,
|
||||||
help='Maximum allowable KB for added files',
|
help='Maxmimum allowable KB for added files',
|
||||||
)
|
)
|
||||||
args = parser.parse_args(argv)
|
|
||||||
|
|
||||||
return find_large_added_files(
|
args = parser.parse_args(argv)
|
||||||
args.filenames,
|
return find_large_added_files(args.filenames, args.maxkb)
|
||||||
args.maxkb,
|
|
||||||
enforce_all=args.enforce_all,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import ast
|
import ast
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from collections.abc import Sequence
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*')
|
parser.add_argument('filenames', nargs='*')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
@ -20,14 +23,18 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
ast.parse(f.read(), filename=filename)
|
ast.parse(f.read(), filename=filename)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
impl = platform.python_implementation()
|
print(
|
||||||
version = sys.version.split()[0]
|
'{}: failed parsing with {} {}:'.format(
|
||||||
print(f'{filename}: failed parsing with {impl} {version}:')
|
filename,
|
||||||
|
platform.python_implementation(),
|
||||||
|
sys.version.partition(' ')[0],
|
||||||
|
),
|
||||||
|
)
|
||||||
tb = ' ' + traceback.format_exc().replace('\n', '\n ')
|
tb = ' ' + traceback.format_exc().replace('\n', '\n ')
|
||||||
print(f'\n{tb}')
|
print('\n{}'.format(tb))
|
||||||
retval = 1
|
retval = 1
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
from __future__ import annotations
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import ast
|
import ast
|
||||||
from collections.abc import Sequence
|
import collections
|
||||||
from typing import NamedTuple
|
import sys
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
|
||||||
BUILTIN_TYPES = {
|
BUILTIN_TYPES = {
|
||||||
|
|
@ -17,48 +21,41 @@ BUILTIN_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Call(NamedTuple):
|
Call = collections.namedtuple('Call', ['name', 'line', 'column'])
|
||||||
name: str
|
|
||||||
line: int
|
|
||||||
column: int
|
|
||||||
|
|
||||||
|
|
||||||
class Visitor(ast.NodeVisitor):
|
class Visitor(ast.NodeVisitor):
|
||||||
def __init__(
|
def __init__(self, ignore=None, allow_dict_kwargs=True):
|
||||||
self,
|
# type: (Optional[Sequence[str]], bool) -> None
|
||||||
ignore: set[str],
|
self.builtin_type_calls = [] # type: List[Call]
|
||||||
allow_dict_kwargs: bool = True,
|
self.ignore = set(ignore) if ignore else set()
|
||||||
) -> None:
|
|
||||||
self.builtin_type_calls: list[Call] = []
|
|
||||||
self.allow_dict_kwargs = allow_dict_kwargs
|
self.allow_dict_kwargs = allow_dict_kwargs
|
||||||
self._disallowed = BUILTIN_TYPES.keys() - ignore
|
|
||||||
|
|
||||||
def _check_dict_call(self, node: ast.Call) -> bool:
|
def _check_dict_call(self, node): # type: (ast.Call) -> bool
|
||||||
return self.allow_dict_kwargs and bool(node.keywords)
|
return (
|
||||||
|
self.allow_dict_kwargs and
|
||||||
|
(getattr(node, 'kwargs', None) or getattr(node, 'keywords', None))
|
||||||
|
)
|
||||||
|
|
||||||
def visit_Call(self, node: ast.Call) -> None:
|
def visit_Call(self, node): # type: (ast.Call) -> None
|
||||||
if (
|
if not isinstance(node.func, ast.Name):
|
||||||
# Ignore functions that are object attributes (`foo.bar()`).
|
# Ignore functions that are object attributes (`foo.bar()`).
|
||||||
# Assume that if the user calls `builtins.list()`, they know what
|
# Assume that if the user calls `builtins.list()`, they know what
|
||||||
# they're doing.
|
# they're doing.
|
||||||
isinstance(node.func, ast.Name) and
|
return
|
||||||
node.func.id in self._disallowed and
|
if node.func.id not in set(BUILTIN_TYPES).difference(self.ignore):
|
||||||
(node.func.id != 'dict' or not self._check_dict_call(node)) and
|
return
|
||||||
not node.args
|
if node.func.id == 'dict' and self._check_dict_call(node):
|
||||||
):
|
return
|
||||||
|
elif node.args:
|
||||||
|
return
|
||||||
self.builtin_type_calls.append(
|
self.builtin_type_calls.append(
|
||||||
Call(node.func.id, node.lineno, node.col_offset),
|
Call(node.func.id, node.lineno, node.col_offset),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.generic_visit(node)
|
|
||||||
|
|
||||||
|
def check_file(filename, ignore=None, allow_dict_kwargs=True):
|
||||||
def check_file(
|
# type: (str, Optional[Sequence[str]], bool) -> List[Call]
|
||||||
filename: str,
|
|
||||||
*,
|
|
||||||
ignore: set[str],
|
|
||||||
allow_dict_kwargs: bool = True,
|
|
||||||
) -> list[Call]:
|
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
tree = ast.parse(f.read(), filename=filename)
|
tree = ast.parse(f.read(), filename=filename)
|
||||||
visitor = Visitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs)
|
visitor = Visitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs)
|
||||||
|
|
@ -66,11 +63,11 @@ def check_file(
|
||||||
return visitor.builtin_type_calls
|
return visitor.builtin_type_calls
|
||||||
|
|
||||||
|
|
||||||
def parse_ignore(value: str) -> set[str]:
|
def parse_ignore(value): # type: (str) -> Set[str]
|
||||||
return set(value.split(','))
|
return set(value.split(','))
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*')
|
parser.add_argument('filenames', nargs='*')
|
||||||
parser.add_argument('--ignore', type=parse_ignore, default=set())
|
parser.add_argument('--ignore', type=parse_ignore, default=set())
|
||||||
|
|
@ -96,11 +93,15 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
rc = rc or 1
|
rc = rc or 1
|
||||||
for call in calls:
|
for call in calls:
|
||||||
print(
|
print(
|
||||||
f'{filename}:{call.line}:{call.column}: '
|
'{filename}:{call.line}:{call.column}: '
|
||||||
f'replace {call.name}() with {BUILTIN_TYPES[call.name]}',
|
'replace {call.name}() with {replacement}'.format(
|
||||||
|
filename=filename,
|
||||||
|
call=call,
|
||||||
|
replacement=BUILTIN_TYPES[call.name],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
27
pre_commit_hooks/check_byte_order_marker.py
Normal file
27
pre_commit_hooks/check_byte_order_marker.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
retv = 0
|
||||||
|
|
||||||
|
for filename in args.filenames:
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
if f.read(3) == b'\xef\xbb\xbf':
|
||||||
|
retv = 1
|
||||||
|
print('{}: Has a byte-order marker'.format(filename))
|
||||||
|
|
||||||
|
return retv
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
exit(main())
|
||||||
|
|
@ -1,35 +1,24 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from collections.abc import Iterable
|
from typing import Iterable
|
||||||
from collections.abc import Iterator
|
from typing import Optional
|
||||||
from collections.abc import Sequence
|
from typing import Sequence
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
from pre_commit_hooks.util import added_files
|
from pre_commit_hooks.util import added_files
|
||||||
from pre_commit_hooks.util import cmd_output
|
from pre_commit_hooks.util import cmd_output
|
||||||
|
|
||||||
|
|
||||||
def lower_set(iterable: Iterable[str]) -> set[str]:
|
def lower_set(iterable): # type: (Iterable[str]) -> Set[str]
|
||||||
return {x.lower() for x in iterable}
|
return {x.lower() for x in iterable}
|
||||||
|
|
||||||
|
|
||||||
def parents(file: str) -> Iterator[str]:
|
def find_conflicting_filenames(filenames): # type: (Sequence[str]) -> int
|
||||||
path_parts = file.split('/')
|
|
||||||
path_parts.pop()
|
|
||||||
while path_parts:
|
|
||||||
yield '/'.join(path_parts)
|
|
||||||
path_parts.pop()
|
|
||||||
|
|
||||||
|
|
||||||
def directories_for(files: set[str]) -> set[str]:
|
|
||||||
return {parent for file in files for parent in parents(file)}
|
|
||||||
|
|
||||||
|
|
||||||
def find_conflicting_filenames(filenames: Sequence[str]) -> int:
|
|
||||||
repo_files = set(cmd_output('git', 'ls-files').splitlines())
|
repo_files = set(cmd_output('git', 'ls-files').splitlines())
|
||||||
repo_files |= directories_for(repo_files)
|
|
||||||
relevant_files = set(filenames) | added_files()
|
relevant_files = set(filenames) | added_files()
|
||||||
relevant_files |= directories_for(relevant_files)
|
|
||||||
repo_files -= relevant_files
|
repo_files -= relevant_files
|
||||||
retv = 0
|
retv = 0
|
||||||
|
|
||||||
|
|
@ -50,13 +39,13 @@ def find_conflicting_filenames(filenames: Sequence[str]) -> int:
|
||||||
if x.lower() in conflicts
|
if x.lower() in conflicts
|
||||||
]
|
]
|
||||||
for filename in sorted(conflicting_files):
|
for filename in sorted(conflicting_files):
|
||||||
print(f'Case-insensitivity conflict found: {filename}')
|
print('Case-insensitivity conflict found: {}'.format(filename))
|
||||||
retv = 1
|
retv = 1
|
||||||
|
|
||||||
return retv
|
return retv
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'filenames', nargs='*',
|
'filenames', nargs='*',
|
||||||
|
|
@ -69,4 +58,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,30 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import io
|
import io
|
||||||
import tokenize
|
import tokenize
|
||||||
from collections.abc import Sequence
|
from typing import Optional
|
||||||
from tokenize import tokenize as tokenize_tokenize
|
from typing import Sequence
|
||||||
|
|
||||||
NON_CODE_TOKENS = frozenset((
|
import six
|
||||||
tokenize.COMMENT, tokenize.ENDMARKER, tokenize.NEWLINE, tokenize.NL,
|
|
||||||
tokenize.ENCODING,
|
if six.PY2: # pragma: no cover (PY2)
|
||||||
))
|
from tokenize import generate_tokens as tokenize_tokenize
|
||||||
|
OTHER_NON_CODE = ()
|
||||||
|
else: # pragma: no cover (PY3)
|
||||||
|
from tokenize import tokenize as tokenize_tokenize
|
||||||
|
OTHER_NON_CODE = (tokenize.ENCODING,)
|
||||||
|
|
||||||
|
NON_CODE_TOKENS = frozenset(
|
||||||
|
(tokenize.COMMENT, tokenize.ENDMARKER, tokenize.NEWLINE, tokenize.NL) +
|
||||||
|
OTHER_NON_CODE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
|
def check_docstring_first(src, filename='<unknown>'):
|
||||||
|
# type: (bytes, str) -> int
|
||||||
"""Returns nonzero if the source has what looks like a docstring that is
|
"""Returns nonzero if the source has what looks like a docstring that is
|
||||||
not at the beginning of the source.
|
not at the beginning of the source.
|
||||||
|
|
||||||
|
|
@ -28,14 +40,18 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
|
||||||
if tok_type == tokenize.STRING and scol == 0:
|
if tok_type == tokenize.STRING and scol == 0:
|
||||||
if found_docstring_line is not None:
|
if found_docstring_line is not None:
|
||||||
print(
|
print(
|
||||||
f'{filename}:{sline}: Multiple module docstrings '
|
'{}:{} Multiple module docstrings '
|
||||||
f'(first docstring on line {found_docstring_line}).',
|
'(first docstring on line {}).'.format(
|
||||||
|
filename, sline, found_docstring_line,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
elif found_code_line is not None:
|
elif found_code_line is not None:
|
||||||
print(
|
print(
|
||||||
f'{filename}:{sline}: Module docstring appears after code '
|
'{}:{} Module docstring appears after code '
|
||||||
f'(code seen on line {found_code_line}).',
|
'(code seen on line {}).'.format(
|
||||||
|
filename, sline, found_code_line,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
|
|
@ -46,7 +62,7 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*')
|
parser.add_argument('filenames', nargs='*')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
|
||||||
|
|
@ -1,85 +1,47 @@
|
||||||
"""Check that executable text files have a shebang."""
|
"""Check that executable text files have a shebang."""
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import shlex
|
import pipes
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Generator
|
from typing import Optional
|
||||||
from collections.abc import Sequence
|
from typing import Sequence
|
||||||
from typing import NamedTuple
|
|
||||||
|
|
||||||
from pre_commit_hooks.util import cmd_output
|
|
||||||
from pre_commit_hooks.util import zsplit
|
|
||||||
|
|
||||||
EXECUTABLE_VALUES = frozenset(('1', '3', '5', '7'))
|
|
||||||
|
|
||||||
|
|
||||||
def check_executables(paths: list[str]) -> int:
|
def check_has_shebang(path): # type: (str) -> int
|
||||||
fs_tracks_executable_bit = cmd_output(
|
|
||||||
'git', 'config', 'core.fileMode', retcode=None,
|
|
||||||
).strip()
|
|
||||||
if fs_tracks_executable_bit == 'false': # pragma: win32 cover
|
|
||||||
return _check_git_filemode(paths)
|
|
||||||
else: # pragma: win32 no cover
|
|
||||||
retv = 0
|
|
||||||
for path in paths:
|
|
||||||
if not has_shebang(path):
|
|
||||||
_message(path)
|
|
||||||
retv = 1
|
|
||||||
|
|
||||||
return retv
|
|
||||||
|
|
||||||
|
|
||||||
class GitLsFile(NamedTuple):
|
|
||||||
mode: str
|
|
||||||
filename: str
|
|
||||||
|
|
||||||
|
|
||||||
def git_ls_files(paths: Sequence[str]) -> Generator[GitLsFile]:
|
|
||||||
outs = cmd_output('git', 'ls-files', '-z', '--stage', '--', *paths)
|
|
||||||
for out in zsplit(outs):
|
|
||||||
metadata, filename = out.split('\t')
|
|
||||||
mode, _, _ = metadata.split()
|
|
||||||
yield GitLsFile(mode, filename)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_git_filemode(paths: Sequence[str]) -> int:
|
|
||||||
seen: set[str] = set()
|
|
||||||
for ls_file in git_ls_files(paths):
|
|
||||||
is_executable = any(b in EXECUTABLE_VALUES for b in ls_file.mode[-3:])
|
|
||||||
if is_executable and not has_shebang(ls_file.filename):
|
|
||||||
_message(ls_file.filename)
|
|
||||||
seen.add(ls_file.filename)
|
|
||||||
|
|
||||||
return int(bool(seen))
|
|
||||||
|
|
||||||
|
|
||||||
def has_shebang(path: str) -> int:
|
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
first_bytes = f.read(2)
|
first_bytes = f.read(2)
|
||||||
|
|
||||||
return first_bytes == b'#!'
|
if first_bytes != b'#!':
|
||||||
|
|
||||||
|
|
||||||
def _message(path: str) -> None:
|
|
||||||
print(
|
print(
|
||||||
f'{path}: marked executable but has no (or invalid) shebang!\n'
|
'{path}: marked executable but has no (or invalid) shebang!\n'
|
||||||
f" If it isn't supposed to be executable, try: "
|
" If it isn't supposed to be executable, try: chmod -x {quoted}\n"
|
||||||
f'`chmod -x {shlex.quote(path)}`\n'
|
' If it is supposed to be executable, double-check its shebang.'
|
||||||
f' If on Windows, you may also need to: '
|
.format(
|
||||||
f'`git add --chmod=-x {shlex.quote(path)}`\n'
|
path=path,
|
||||||
f' If it is supposed to be executable, double-check its shebang.',
|
quoted=pipes.quote(path),
|
||||||
|
),
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument('filenames', nargs='*')
|
parser.add_argument('filenames', nargs='*')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
return check_executables(args.filenames)
|
retv = 0
|
||||||
|
|
||||||
|
for filename in args.filenames:
|
||||||
|
retv |= check_has_shebang(filename)
|
||||||
|
|
||||||
|
return retv
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,27 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
from collections.abc import Sequence
|
import sys
|
||||||
from typing import Any
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
def raise_duplicate_keys(
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
ordered_pairs: list[tuple[str, Any]],
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
d = {}
|
|
||||||
for key, val in ordered_pairs:
|
|
||||||
if key in d:
|
|
||||||
raise ValueError(f'Duplicate key: {key}')
|
|
||||||
else:
|
|
||||||
d[key] = val
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
|
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
retval = 0
|
retval = 0
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
with open(filename, 'rb') as f:
|
|
||||||
try:
|
try:
|
||||||
json.load(f, object_pairs_hook=raise_duplicate_keys)
|
json.load(io.open(filename, encoding='UTF-8'))
|
||||||
except ValueError as exc:
|
except (ValueError, UnicodeDecodeError) as exc:
|
||||||
print(f'{filename}: Failed to json decode ({exc})')
|
print('{}: Failed to json decode ({})'.format(filename, exc))
|
||||||
retval = 1
|
retval = 1
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,32 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os.path
|
import os.path
|
||||||
from collections.abc import Sequence
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
from pre_commit_hooks.util import cmd_output
|
|
||||||
|
|
||||||
|
|
||||||
CONFLICT_PATTERNS = [
|
CONFLICT_PATTERNS = [
|
||||||
b'<<<<<<< ',
|
b'<<<<<<< ',
|
||||||
b'======= ',
|
b'======= ',
|
||||||
b'=======\r\n',
|
|
||||||
b'=======\n',
|
b'=======\n',
|
||||||
b'>>>>>>> ',
|
b'>>>>>>> ',
|
||||||
]
|
]
|
||||||
|
WARNING_MSG = 'Merge conflict string "{0}" found in {1}:{2}'
|
||||||
|
|
||||||
|
|
||||||
def is_in_merge() -> bool:
|
def is_in_merge(): # type: () -> int
|
||||||
git_dir = cmd_output('git', 'rev-parse', '--git-dir').rstrip()
|
|
||||||
return (
|
return (
|
||||||
os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and
|
os.path.exists(os.path.join('.git', 'MERGE_MSG')) and
|
||||||
(
|
(
|
||||||
os.path.exists(os.path.join(git_dir, 'MERGE_HEAD')) or
|
os.path.exists(os.path.join('.git', 'MERGE_HEAD')) or
|
||||||
os.path.exists(os.path.join(git_dir, 'rebase-apply')) or
|
os.path.exists(os.path.join('.git', 'rebase-apply')) or
|
||||||
os.path.exists(os.path.join(git_dir, 'rebase-merge'))
|
os.path.exists(os.path.join('.git', 'rebase-merge'))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*')
|
parser.add_argument('filenames', nargs='*')
|
||||||
parser.add_argument('--assume-in-merge', action='store_true')
|
parser.add_argument('--assume-in-merge', action='store_true')
|
||||||
|
|
@ -40,12 +38,13 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
retcode = 0
|
retcode = 0
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
with open(filename, 'rb') as inputfile:
|
with open(filename, 'rb') as inputfile:
|
||||||
for i, line in enumerate(inputfile, start=1):
|
for i, line in enumerate(inputfile):
|
||||||
for pattern in CONFLICT_PATTERNS:
|
for pattern in CONFLICT_PATTERNS:
|
||||||
if line.startswith(pattern):
|
if line.startswith(pattern):
|
||||||
print(
|
print(
|
||||||
f'{filename}:{i}: Merge conflict string '
|
WARNING_MSG.format(
|
||||||
f'{pattern.strip().decode()!r} found',
|
pattern.decode(), filename, i + 1,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
retcode = 1
|
retcode = 1
|
||||||
|
|
||||||
|
|
@ -53,4 +52,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
"""Check that text files with a shebang are executable."""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import shlex
|
|
||||||
import sys
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
from pre_commit_hooks.check_executables_have_shebangs import EXECUTABLE_VALUES
|
|
||||||
from pre_commit_hooks.check_executables_have_shebangs import git_ls_files
|
|
||||||
from pre_commit_hooks.check_executables_have_shebangs import has_shebang
|
|
||||||
|
|
||||||
|
|
||||||
def check_shebangs(paths: list[str]) -> int:
|
|
||||||
# Cannot optimize on non-executability here if we intend this check to
|
|
||||||
# work on win32 -- and that's where problems caused by non-executability
|
|
||||||
# (elsewhere) are most likely to arise from.
|
|
||||||
return _check_git_filemode(paths)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_git_filemode(paths: Sequence[str]) -> int:
|
|
||||||
seen: set[str] = set()
|
|
||||||
for ls_file in git_ls_files(paths):
|
|
||||||
is_executable = any(b in EXECUTABLE_VALUES for b in ls_file.mode[-3:])
|
|
||||||
if not is_executable and has_shebang(ls_file.filename):
|
|
||||||
_message(ls_file.filename)
|
|
||||||
seen.add(ls_file.filename)
|
|
||||||
|
|
||||||
return int(bool(seen))
|
|
||||||
|
|
||||||
|
|
||||||
def _message(path: str) -> None:
|
|
||||||
print(
|
|
||||||
f'{path}: has a shebang but is not marked executable!\n'
|
|
||||||
f' If it is supposed to be executable, try: '
|
|
||||||
f'`chmod +x {shlex.quote(path)}`\n'
|
|
||||||
f' If on Windows, you may also need to: '
|
|
||||||
f'`git add --chmod=+x {shlex.quote(path)}`\n'
|
|
||||||
f' If it is not supposed to be executable, double-check its shebang '
|
|
||||||
f'is wanted.\n',
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.add_argument('filenames', nargs='*')
|
|
||||||
args = parser.parse_args(argv)
|
|
||||||
|
|
||||||
return check_shebangs(args.filenames)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
raise SystemExit(main())
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os.path
|
import os.path
|
||||||
from collections.abc import Sequence
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser(description='Checks for broken symlinks.')
|
parser = argparse.ArgumentParser(description='Checks for broken symlinks.')
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
@ -17,11 +20,11 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
os.path.islink(filename) and
|
os.path.islink(filename) and
|
||||||
not os.path.exists(filename)
|
not os.path.exists(filename)
|
||||||
): # pragma: no cover (symlink support required)
|
): # pragma: no cover (symlink support required)
|
||||||
print(f'{filename}: Broken symlink')
|
print('{}: Broken symlink'.format(filename))
|
||||||
retv = 1
|
retv = 1
|
||||||
|
|
||||||
return retv
|
return retv
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Sequence
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
if sys.version_info >= (3, 11): # pragma: >=3.11 cover
|
import toml
|
||||||
import tomllib
|
|
||||||
else: # pragma: <3.11 cover
|
|
||||||
import tomli as tomllib
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
|
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
@ -18,13 +16,13 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
retval = 0
|
retval = 0
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
try:
|
try:
|
||||||
with open(filename, mode='rb') as fp:
|
with open(filename) as f:
|
||||||
tomllib.load(fp)
|
toml.load(f)
|
||||||
except tomllib.TOMLDecodeError as exc:
|
except toml.TomlDecodeError as exc:
|
||||||
print(f'{filename}: {exc}')
|
print('{}: {}'.format(filename, exc))
|
||||||
retval = 1
|
retval = 1
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,39 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Sequence
|
from typing import Optional
|
||||||
from re import Pattern
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
def _get_pattern(domain: str) -> Pattern[bytes]:
|
GITHUB_NON_PERMALINK = re.compile(
|
||||||
regex = (
|
br'https://github.com/[^/ ]+/[^/ ]+/blob/master/[^# ]+#L\d+',
|
||||||
rf'https://{domain}/[^/ ]+/[^/ ]+/blob/'
|
)
|
||||||
r'(?![a-fA-F0-9]{4,64}/)([^/. ]+)/[^# ]+#L\d+'
|
|
||||||
)
|
|
||||||
return re.compile(regex.encode())
|
|
||||||
|
|
||||||
|
|
||||||
def _check_filename(filename: str, patterns: list[Pattern[bytes]]) -> int:
|
def _check_filename(filename): # type: (str) -> int
|
||||||
retv = 0
|
retv = 0
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
for i, line in enumerate(f, 1):
|
for i, line in enumerate(f, 1):
|
||||||
for pattern in patterns:
|
if GITHUB_NON_PERMALINK.search(line):
|
||||||
if pattern.search(line):
|
sys.stdout.write('{}:{}:'.format(filename, i))
|
||||||
sys.stdout.write(f'{filename}:{i}:')
|
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
sys.stdout.buffer.write(line)
|
getattr(sys.stdout, 'buffer', sys.stdout).write(line)
|
||||||
retv = 1
|
retv = 1
|
||||||
return retv
|
return retv
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*')
|
parser.add_argument('filenames', nargs='*')
|
||||||
parser.add_argument(
|
|
||||||
'--additional-github-domain',
|
|
||||||
dest='additional_github_domains',
|
|
||||||
action='append',
|
|
||||||
default=['github.com'],
|
|
||||||
)
|
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
patterns = [
|
|
||||||
_get_pattern(domain)
|
|
||||||
for domain in args.additional_github_domains
|
|
||||||
]
|
|
||||||
|
|
||||||
retv = 0
|
retv = 0
|
||||||
|
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
retv |= _check_filename(filename, patterns)
|
retv |= _check_filename(filename)
|
||||||
|
|
||||||
if retv:
|
if retv:
|
||||||
print()
|
print()
|
||||||
|
|
@ -57,4 +43,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,30 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import io
|
||||||
|
import sys
|
||||||
import xml.sax.handler
|
import xml.sax.handler
|
||||||
from collections.abc import Sequence
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*', help='XML filenames to check.')
|
parser.add_argument('filenames', nargs='*', help='XML filenames to check.')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
retval = 0
|
retval = 0
|
||||||
handler = xml.sax.handler.ContentHandler()
|
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
try:
|
try:
|
||||||
with open(filename, 'rb') as xml_file:
|
with io.open(filename, 'rb') as xml_file:
|
||||||
xml.sax.parse(xml_file, handler)
|
xml.sax.parse(xml_file, xml.sax.handler.ContentHandler())
|
||||||
except xml.sax.SAXException as exc:
|
except xml.sax.SAXException as exc:
|
||||||
print(f'{filename}: Failed to xml parse ({exc})')
|
print('{}: Failed to xml parse ({})'.format(filename, exc))
|
||||||
retval = 1
|
retval = 1
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,33 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from collections.abc import Generator
|
import collections
|
||||||
from collections.abc import Sequence
|
import io
|
||||||
|
import sys
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import NamedTuple
|
from typing import Generator
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
import ruamel.yaml
|
import ruamel.yaml
|
||||||
|
|
||||||
yaml = ruamel.yaml.YAML(typ='safe')
|
yaml = ruamel.yaml.YAML(typ='safe')
|
||||||
|
|
||||||
|
|
||||||
def _exhaust(gen: Generator[str]) -> None:
|
def _exhaust(gen): # type: (Generator[str, None, None]) -> None
|
||||||
for _ in gen:
|
for _ in gen:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _parse_unsafe(*args: Any, **kwargs: Any) -> None:
|
def _parse_unsafe(*args, **kwargs): # type: (*Any, **Any) -> None
|
||||||
_exhaust(yaml.parse(*args, **kwargs))
|
_exhaust(yaml.parse(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
def _load_all(*args: Any, **kwargs: Any) -> None:
|
def _load_all(*args, **kwargs): # type: (*Any, **Any) -> None
|
||||||
_exhaust(yaml.load_all(*args, **kwargs))
|
_exhaust(yaml.load_all(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
class Key(NamedTuple):
|
Key = collections.namedtuple('Key', ('multi', 'unsafe'))
|
||||||
multi: bool
|
|
||||||
unsafe: bool
|
|
||||||
|
|
||||||
|
|
||||||
LOAD_FNS = {
|
LOAD_FNS = {
|
||||||
Key(multi=False, unsafe=False): yaml.load,
|
Key(multi=False, unsafe=False): yaml.load,
|
||||||
Key(multi=False, unsafe=True): _parse_unsafe,
|
Key(multi=False, unsafe=True): _parse_unsafe,
|
||||||
|
|
@ -37,7 +36,7 @@ LOAD_FNS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-m', '--multi', '--allow-multiple-documents', action='store_true',
|
'-m', '--multi', '--allow-multiple-documents', action='store_true',
|
||||||
|
|
@ -46,7 +45,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
'--unsafe', action='store_true',
|
'--unsafe', action='store_true',
|
||||||
help=(
|
help=(
|
||||||
'Instead of loading the files, simply parse them for syntax. '
|
'Instead of loading the files, simply parse them for syntax. '
|
||||||
'A syntax-only check enables extensions and unsafe constructs '
|
'A syntax-only check enables extensions and unsafe contstructs '
|
||||||
'which would otherwise be forbidden. Using this option removes '
|
'which would otherwise be forbidden. Using this option removes '
|
||||||
'all guarantees of portability to other yaml implementations. '
|
'all guarantees of portability to other yaml implementations. '
|
||||||
'Implies --allow-multiple-documents'
|
'Implies --allow-multiple-documents'
|
||||||
|
|
@ -60,7 +59,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
retval = 0
|
retval = 0
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
try:
|
try:
|
||||||
with open(filename, encoding='UTF-8') as f:
|
with io.open(filename, encoding='UTF-8') as f:
|
||||||
load_fn(f)
|
load_fn(f)
|
||||||
except ruamel.yaml.YAMLError as exc:
|
except ruamel.yaml.YAMLError as exc:
|
||||||
print(exc)
|
print(exc)
|
||||||
|
|
@ -69,4 +68,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,35 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import ast
|
import ast
|
||||||
|
import collections
|
||||||
import traceback
|
import traceback
|
||||||
from collections.abc import Sequence
|
from typing import List
|
||||||
from typing import NamedTuple
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
DEBUG_STATEMENTS = {
|
DEBUG_STATEMENTS = {'pdb', 'ipdb', 'pudb', 'q', 'rdb', 'rpdb'}
|
||||||
'bpdb',
|
Debug = collections.namedtuple('Debug', ('line', 'col', 'name', 'reason'))
|
||||||
'ipdb',
|
|
||||||
'pdb',
|
|
||||||
'pdbr',
|
|
||||||
'pudb',
|
|
||||||
'pydevd_pycharm',
|
|
||||||
'q',
|
|
||||||
'rdb',
|
|
||||||
'rpdb',
|
|
||||||
'wdb',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Debug(NamedTuple):
|
|
||||||
line: int
|
|
||||||
col: int
|
|
||||||
name: str
|
|
||||||
reason: str
|
|
||||||
|
|
||||||
|
|
||||||
class DebugStatementParser(ast.NodeVisitor):
|
class DebugStatementParser(ast.NodeVisitor):
|
||||||
def __init__(self) -> None:
|
def __init__(self): # type: () -> None
|
||||||
self.breakpoints: list[Debug] = []
|
self.breakpoints = [] # type: List[Debug]
|
||||||
|
|
||||||
def visit_Import(self, node: ast.Import) -> None:
|
def visit_Import(self, node): # type: (ast.Import) -> None
|
||||||
for name in node.names:
|
for name in node.names:
|
||||||
if name.name in DEBUG_STATEMENTS:
|
if name.name in DEBUG_STATEMENTS:
|
||||||
st = Debug(node.lineno, node.col_offset, name.name, 'imported')
|
st = Debug(node.lineno, node.col_offset, name.name, 'imported')
|
||||||
self.breakpoints.append(st)
|
self.breakpoints.append(st)
|
||||||
|
|
||||||
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
def visit_ImportFrom(self, node): # type: (ast.ImportFrom) -> None
|
||||||
if node.module in DEBUG_STATEMENTS:
|
if node.module in DEBUG_STATEMENTS:
|
||||||
st = Debug(node.lineno, node.col_offset, node.module, 'imported')
|
st = Debug(node.lineno, node.col_offset, node.module, 'imported')
|
||||||
self.breakpoints.append(st)
|
self.breakpoints.append(st)
|
||||||
|
|
||||||
def visit_Call(self, node: ast.Call) -> None:
|
def visit_Call(self, node): # type: (ast.Call) -> None
|
||||||
"""python3.7+ breakpoint()"""
|
"""python3.7+ breakpoint()"""
|
||||||
if isinstance(node.func, ast.Name) and node.func.id == 'breakpoint':
|
if isinstance(node.func, ast.Name) and node.func.id == 'breakpoint':
|
||||||
st = Debug(node.lineno, node.col_offset, node.func.id, 'called')
|
st = Debug(node.lineno, node.col_offset, node.func.id, 'called')
|
||||||
|
|
@ -51,12 +37,12 @@ class DebugStatementParser(ast.NodeVisitor):
|
||||||
self.generic_visit(node)
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
|
||||||
def check_file(filename: str) -> int:
|
def check_file(filename): # type: (str) -> int
|
||||||
try:
|
try:
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
ast_obj = ast.parse(f.read(), filename=filename)
|
ast_obj = ast.parse(f.read(), filename=filename)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
print(f'{filename} - Could not parse ast')
|
print('{} - Could not parse ast'.format(filename))
|
||||||
print()
|
print()
|
||||||
print('\t' + traceback.format_exc().replace('\n', '\n\t'))
|
print('\t' + traceback.format_exc().replace('\n', '\n\t'))
|
||||||
print()
|
print()
|
||||||
|
|
@ -66,12 +52,16 @@ def check_file(filename: str) -> int:
|
||||||
visitor.visit(ast_obj)
|
visitor.visit(ast_obj)
|
||||||
|
|
||||||
for bp in visitor.breakpoints:
|
for bp in visitor.breakpoints:
|
||||||
print(f'{filename}:{bp.line}:{bp.col}: {bp.name} {bp.reason}')
|
print(
|
||||||
|
'{}:{}:{} - {} {}'.format(
|
||||||
|
filename, bp.line, bp.col, bp.name, bp.reason,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return int(bool(visitor.breakpoints))
|
return int(bool(visitor.breakpoints))
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to run')
|
parser.add_argument('filenames', nargs='*', help='Filenames to run')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
@ -83,4 +73,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import shlex
|
|
||||||
import subprocess
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
from pre_commit_hooks.util import cmd_output
|
|
||||||
from pre_commit_hooks.util import zsplit
|
|
||||||
|
|
||||||
ORDINARY_CHANGED_ENTRIES_MARKER = '1'
|
|
||||||
PERMS_LINK = '120000'
|
|
||||||
PERMS_NONEXIST = '000000'
|
|
||||||
|
|
||||||
|
|
||||||
def find_destroyed_symlinks(files: Sequence[str]) -> list[str]:
|
|
||||||
destroyed_links: list[str] = []
|
|
||||||
if not files:
|
|
||||||
return destroyed_links
|
|
||||||
for line in zsplit(
|
|
||||||
cmd_output('git', 'status', '--porcelain=v2', '-z', '--', *files),
|
|
||||||
):
|
|
||||||
splitted = line.split(' ')
|
|
||||||
if splitted and splitted[0] == ORDINARY_CHANGED_ENTRIES_MARKER:
|
|
||||||
# https://git-scm.com/docs/git-status#_changed_tracked_entries
|
|
||||||
(
|
|
||||||
_, _, _,
|
|
||||||
mode_HEAD,
|
|
||||||
mode_index,
|
|
||||||
_,
|
|
||||||
hash_HEAD,
|
|
||||||
hash_index,
|
|
||||||
*path_splitted,
|
|
||||||
) = splitted
|
|
||||||
path = ' '.join(path_splitted)
|
|
||||||
if (
|
|
||||||
mode_HEAD == PERMS_LINK and
|
|
||||||
mode_index != PERMS_LINK and
|
|
||||||
mode_index != PERMS_NONEXIST
|
|
||||||
):
|
|
||||||
if hash_HEAD == hash_index:
|
|
||||||
# if old and new hashes are equal, it's not needed to check
|
|
||||||
# anything more, we've found a destroyed symlink for sure
|
|
||||||
destroyed_links.append(path)
|
|
||||||
else:
|
|
||||||
# if old and new hashes are *not* equal, it doesn't mean
|
|
||||||
# that everything is OK - new file may be altered
|
|
||||||
# by something like trailing-whitespace and/or
|
|
||||||
# mixed-line-ending hooks so we need to go deeper
|
|
||||||
SIZE_CMD = ('git', 'cat-file', '-s')
|
|
||||||
size_index = int(cmd_output(*SIZE_CMD, hash_index).strip())
|
|
||||||
size_HEAD = int(cmd_output(*SIZE_CMD, hash_HEAD).strip())
|
|
||||||
|
|
||||||
# in the worst case new file may have CRLF added
|
|
||||||
# so check content only if new file is bigger
|
|
||||||
# not more than 2 bytes compared to the old one
|
|
||||||
if size_index <= size_HEAD + 2:
|
|
||||||
head_content = subprocess.check_output(
|
|
||||||
('git', 'cat-file', '-p', hash_HEAD),
|
|
||||||
).rstrip()
|
|
||||||
index_content = subprocess.check_output(
|
|
||||||
('git', 'cat-file', '-p', hash_index),
|
|
||||||
).rstrip()
|
|
||||||
if head_content == index_content:
|
|
||||||
destroyed_links.append(path)
|
|
||||||
return destroyed_links
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
|
|
||||||
args = parser.parse_args(argv)
|
|
||||||
destroyed_links = find_destroyed_symlinks(files=args.filenames)
|
|
||||||
if destroyed_links:
|
|
||||||
print('Destroyed symlinks:')
|
|
||||||
for destroyed_link in destroyed_links:
|
|
||||||
print(f'- {destroyed_link}')
|
|
||||||
print('You should unstage affected files:')
|
|
||||||
print(f'\tgit reset HEAD -- {shlex.join(destroyed_links)}')
|
|
||||||
print(
|
|
||||||
'And retry commit. As a long term solution '
|
|
||||||
'you may try to explicitly tell git that your '
|
|
||||||
'environment does not support symlinks:',
|
|
||||||
)
|
|
||||||
print('\tgit config core.symlinks false')
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
raise SystemExit(main())
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import configparser
|
|
||||||
import os
|
import os
|
||||||
from collections.abc import Sequence
|
from typing import Dict
|
||||||
from typing import NamedTuple
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from six.moves import configparser
|
||||||
|
|
||||||
|
|
||||||
class BadFile(NamedTuple):
|
def get_aws_cred_files_from_env(): # type: () -> Set[str]
|
||||||
filename: str
|
|
||||||
key: str
|
|
||||||
|
|
||||||
|
|
||||||
def get_aws_cred_files_from_env() -> set[str]:
|
|
||||||
"""Extract credential file paths from environment variables."""
|
"""Extract credential file paths from environment variables."""
|
||||||
return {
|
return {
|
||||||
os.environ[env_var]
|
os.environ[env_var]
|
||||||
|
|
@ -24,18 +24,18 @@ def get_aws_cred_files_from_env() -> set[str]:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_aws_secrets_from_env() -> set[str]:
|
def get_aws_secrets_from_env(): # type: () -> Set[str]
|
||||||
"""Extract AWS secrets from environment variables."""
|
"""Extract AWS secrets from environment variables."""
|
||||||
keys = set()
|
keys = set()
|
||||||
for env_var in (
|
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 os.environ.get(env_var):
|
if env_var in os.environ:
|
||||||
keys.add(os.environ[env_var])
|
keys.add(os.environ[env_var])
|
||||||
return keys
|
return keys
|
||||||
|
|
||||||
|
|
||||||
def get_aws_secrets_from_file(credentials_file: str) -> set[str]:
|
def get_aws_secrets_from_file(credentials_file): # type: (str) -> Set[str]
|
||||||
"""Extract AWS secrets from configuration files.
|
"""Extract AWS secrets from configuration files.
|
||||||
|
|
||||||
Read an ini-style configuration file and return a set with all found AWS
|
Read an ini-style configuration file and return a set with all found AWS
|
||||||
|
|
@ -66,10 +66,8 @@ def get_aws_secrets_from_file(credentials_file: str) -> set[str]:
|
||||||
return keys
|
return keys
|
||||||
|
|
||||||
|
|
||||||
def check_file_for_aws_keys(
|
def check_file_for_aws_keys(filenames, keys):
|
||||||
filenames: Sequence[str],
|
# type: (Sequence[str], Set[str]) -> List[Dict[str, str]]
|
||||||
keys: set[bytes],
|
|
||||||
) -> list[BadFile]:
|
|
||||||
"""Check if files contain AWS secrets.
|
"""Check if files contain AWS secrets.
|
||||||
|
|
||||||
Return a list of all files containing AWS secrets and keys found, with all
|
Return a list of all files containing AWS secrets and keys found, with all
|
||||||
|
|
@ -78,18 +76,19 @@ def check_file_for_aws_keys(
|
||||||
bad_files = []
|
bad_files = []
|
||||||
|
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
with open(filename, 'rb') as content:
|
with open(filename, 'r') as content:
|
||||||
text_body = content.read()
|
text_body = content.read()
|
||||||
for key in keys:
|
for key in keys:
|
||||||
# naively match the entire file, low chance of incorrect
|
# naively match the entire file, low chance of incorrect
|
||||||
# collision
|
# collision
|
||||||
if key in text_body:
|
if key in text_body:
|
||||||
key_hidden = key.decode()[:4].ljust(28, '*')
|
bad_files.append({
|
||||||
bad_files.append(BadFile(filename, key_hidden))
|
'filename': filename, 'key': key[:4] + '*' * 28,
|
||||||
|
})
|
||||||
return bad_files
|
return bad_files
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='+', help='Filenames to run')
|
parser.add_argument('filenames', nargs='+', help='Filenames to run')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|
@ -118,7 +117,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
# of files to to gather AWS secrets from.
|
# of files to to gather AWS secrets from.
|
||||||
credential_files |= get_aws_cred_files_from_env()
|
credential_files |= get_aws_cred_files_from_env()
|
||||||
|
|
||||||
keys: set[str] = set()
|
keys = set() # type: Set[str]
|
||||||
for credential_file in credential_files:
|
for credential_file in credential_files:
|
||||||
keys |= get_aws_secrets_from_file(credential_file)
|
keys |= get_aws_secrets_from_file(credential_file)
|
||||||
|
|
||||||
|
|
@ -137,15 +136,14 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
)
|
)
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
keys_b = {key.encode() for key in keys}
|
bad_filenames = check_file_for_aws_keys(args.filenames, keys)
|
||||||
bad_filenames = check_file_for_aws_keys(args.filenames, keys_b)
|
|
||||||
if bad_filenames:
|
if bad_filenames:
|
||||||
for bad_file in bad_filenames:
|
for bad_file in bad_filenames:
|
||||||
print(f'AWS secret found in {bad_file.filename}: {bad_file.key}')
|
print('AWS secret found in {filename}: {key}'.format(**bad_file))
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from collections.abc import Sequence
|
import sys
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
BLACKLIST = [
|
BLACKLIST = [
|
||||||
b'BEGIN RSA PRIVATE KEY',
|
b'BEGIN RSA PRIVATE KEY',
|
||||||
|
|
@ -12,12 +14,10 @@ BLACKLIST = [
|
||||||
b'PuTTY-User-Key-File-2',
|
b'PuTTY-User-Key-File-2',
|
||||||
b'BEGIN SSH2 ENCRYPTED PRIVATE KEY',
|
b'BEGIN SSH2 ENCRYPTED PRIVATE KEY',
|
||||||
b'BEGIN PGP PRIVATE KEY BLOCK',
|
b'BEGIN PGP PRIVATE KEY BLOCK',
|
||||||
b'BEGIN ENCRYPTED PRIVATE KEY',
|
|
||||||
b'BEGIN OpenVPN Static key V1',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
@ -32,11 +32,11 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
if private_key_files:
|
if private_key_files:
|
||||||
for private_key_file in private_key_files:
|
for private_key_file in private_key_files:
|
||||||
print(f'Private key found: {private_key_file}')
|
print('Private key found: {}'.format(private_key_file))
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
from collections.abc import Sequence
|
import sys
|
||||||
from typing import IO
|
from typing import IO
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
def fix_file(file_obj: IO[bytes]) -> int:
|
def fix_file(file_obj): # type: (IO[bytes]) -> int
|
||||||
# Test for newline at end of file
|
# Test for newline at end of file
|
||||||
# Empty files will throw IOError here
|
# Empty files will throw IOError here
|
||||||
try:
|
try:
|
||||||
file_obj.seek(-1, os.SEEK_END)
|
file_obj.seek(-1, os.SEEK_END)
|
||||||
except OSError:
|
except IOError:
|
||||||
return 0
|
return 0
|
||||||
last_character = file_obj.read(1)
|
last_character = file_obj.read(1)
|
||||||
# last_character will be '' for an empty file
|
# last_character will be '' for an empty file
|
||||||
|
|
@ -49,7 +52,7 @@ def fix_file(file_obj: IO[bytes]) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
@ -61,11 +64,11 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
with open(filename, 'rb+') as file_obj:
|
with open(filename, 'rb+') as file_obj:
|
||||||
ret_for_file = fix_file(file_obj)
|
ret_for_file = fix_file(file_obj)
|
||||||
if ret_for_file:
|
if ret_for_file:
|
||||||
print(f'Fixing {filename}')
|
print('Fixing {}'.format(filename))
|
||||||
retv |= ret_for_file
|
retv |= ret_for_file
|
||||||
|
|
||||||
return retv
|
return retv
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -2,45 +2,31 @@
|
||||||
A very simple pre-commit hook that, when passed one or more filenames
|
A very simple pre-commit hook that, when passed one or more filenames
|
||||||
as arguments, will sort the lines in those files.
|
as arguments, will sort the lines in those files.
|
||||||
|
|
||||||
An example use case for this: you have a deploy-allowlist.txt file
|
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
|
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
|
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
|
per line. Various users are adding/removing lines from this file; using
|
||||||
this hook on that file should reduce the instances of git merge
|
this hook on that file should reduce the instances of git merge
|
||||||
conflicts and keep the file nicely ordered.
|
conflicts and keep the file nicely ordered.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from collections.abc import Callable
|
import sys
|
||||||
from collections.abc import Iterable
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
|
||||||
from typing import IO
|
from typing import IO
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
PASS = 0
|
PASS = 0
|
||||||
FAIL = 1
|
FAIL = 1
|
||||||
|
|
||||||
|
|
||||||
def sort_file_contents(
|
def sort_file_contents(f): # type: (IO[bytes]) -> int
|
||||||
f: IO[bytes],
|
|
||||||
key: Callable[[bytes], Any] | None,
|
|
||||||
*,
|
|
||||||
unique: bool = False,
|
|
||||||
) -> int:
|
|
||||||
before = list(f)
|
before = list(f)
|
||||||
lines: Iterable[bytes] = (
|
after = sorted([line.strip(b'\n\r') for line in before if line.strip()])
|
||||||
line.rstrip(b'\n\r') for line in before if line.strip()
|
|
||||||
)
|
|
||||||
if unique:
|
|
||||||
lines = set(lines)
|
|
||||||
after = sorted(lines, key=key)
|
|
||||||
|
|
||||||
before_string = b''.join(before)
|
before_string = b''.join(before)
|
||||||
after_string = b'\n'.join(after)
|
after_string = b'\n'.join(after) + b'\n'
|
||||||
|
|
||||||
if after_string:
|
|
||||||
after_string += b'\n'
|
|
||||||
|
|
||||||
if before_string == after_string:
|
if before_string == after_string:
|
||||||
return PASS
|
return PASS
|
||||||
|
|
@ -51,36 +37,19 @@ def sort_file_contents(
|
||||||
return FAIL
|
return FAIL
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='+', help='Files to sort')
|
parser.add_argument('filenames', nargs='+', help='Files to sort')
|
||||||
|
|
||||||
mutex = parser.add_mutually_exclusive_group(required=False)
|
|
||||||
mutex.add_argument(
|
|
||||||
'--ignore-case',
|
|
||||||
action='store_const',
|
|
||||||
const=bytes.lower,
|
|
||||||
default=None,
|
|
||||||
help='fold lower case to upper case characters',
|
|
||||||
)
|
|
||||||
mutex.add_argument(
|
|
||||||
'--unique',
|
|
||||||
action='store_true',
|
|
||||||
help='ensure each line is unique',
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
retv = PASS
|
retv = PASS
|
||||||
|
|
||||||
for arg in args.filenames:
|
for arg in args.filenames:
|
||||||
with open(arg, 'rb+') as file_obj:
|
with open(arg, 'rb+') as file_obj:
|
||||||
ret_for_file = sort_file_contents(
|
ret_for_file = sort_file_contents(file_obj)
|
||||||
file_obj, key=args.ignore_case, unique=args.unique,
|
|
||||||
)
|
|
||||||
|
|
||||||
if ret_for_file:
|
if ret_for_file:
|
||||||
print(f'Sorting {arg}')
|
print('Sorting {}'.format(arg))
|
||||||
|
|
||||||
retv |= ret_for_file
|
retv |= ret_for_file
|
||||||
|
|
||||||
|
|
@ -88,4 +57,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
|
||||||
args = parser.parse_args(argv)
|
|
||||||
|
|
||||||
retv = 0
|
|
||||||
|
|
||||||
for filename in args.filenames:
|
|
||||||
with open(filename, 'rb') as f_b:
|
|
||||||
bts = f_b.read(3)
|
|
||||||
|
|
||||||
if bts == b'\xef\xbb\xbf':
|
|
||||||
with open(filename, newline='', encoding='utf-8-sig') as f:
|
|
||||||
contents = f.read()
|
|
||||||
with open(filename, 'w', newline='', encoding='utf-8') as f:
|
|
||||||
f.write(contents)
|
|
||||||
|
|
||||||
print(f'{filename}: removed byte-order marker')
|
|
||||||
retv = 1
|
|
||||||
|
|
||||||
return retv
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
raise SystemExit(main())
|
|
||||||
153
pre_commit_hooks/fix_encoding_pragma.py
Normal file
153
pre_commit_hooks/fix_encoding_pragma.py
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
from typing import IO
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
DEFAULT_PRAGMA = b'# -*- coding: utf-8 -*-'
|
||||||
|
|
||||||
|
|
||||||
|
def has_coding(line): # type: (bytes) -> bool
|
||||||
|
if not line.strip():
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
line.lstrip()[:1] == b'#' and (
|
||||||
|
b'unicode' in line or
|
||||||
|
b'encoding' in line or
|
||||||
|
b'coding:' in line or
|
||||||
|
b'coding=' in line
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ExpectedContents(
|
||||||
|
collections.namedtuple(
|
||||||
|
'ExpectedContents', ('shebang', 'rest', 'pragma_status', 'ending'),
|
||||||
|
),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
pragma_status:
|
||||||
|
- True: has exactly the coding pragma expected
|
||||||
|
- False: missing coding pragma entirely
|
||||||
|
- None: has a coding pragma, but it does not match
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_any_pragma(self): # type: () -> bool
|
||||||
|
return self.pragma_status is not False
|
||||||
|
|
||||||
|
def is_expected_pragma(self, remove): # type: (bool) -> bool
|
||||||
|
expected_pragma_status = not remove
|
||||||
|
return self.pragma_status is expected_pragma_status
|
||||||
|
|
||||||
|
|
||||||
|
def _get_expected_contents(first_line, second_line, rest, expected_pragma):
|
||||||
|
# type: (bytes, bytes, bytes, bytes) -> ExpectedContents
|
||||||
|
ending = b'\r\n' if first_line.endswith(b'\r\n') else b'\n'
|
||||||
|
|
||||||
|
if first_line.startswith(b'#!'):
|
||||||
|
shebang = first_line
|
||||||
|
potential_coding = second_line
|
||||||
|
else:
|
||||||
|
shebang = b''
|
||||||
|
potential_coding = first_line
|
||||||
|
rest = second_line + rest
|
||||||
|
|
||||||
|
if potential_coding.rstrip(b'\r\n') == expected_pragma:
|
||||||
|
pragma_status = True # type: Optional[bool]
|
||||||
|
elif has_coding(potential_coding):
|
||||||
|
pragma_status = None
|
||||||
|
else:
|
||||||
|
pragma_status = False
|
||||||
|
rest = potential_coding + rest
|
||||||
|
|
||||||
|
return ExpectedContents(
|
||||||
|
shebang=shebang, rest=rest, pragma_status=pragma_status, ending=ending,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fix_encoding_pragma(f, remove=False, expected_pragma=DEFAULT_PRAGMA):
|
||||||
|
# type: (IO[bytes], bool, bytes) -> int
|
||||||
|
expected = _get_expected_contents(
|
||||||
|
f.readline(), f.readline(), f.read(), expected_pragma,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Special cases for empty files
|
||||||
|
if not expected.rest.strip():
|
||||||
|
# If a file only has a shebang or a coding pragma, remove it
|
||||||
|
if expected.has_any_pragma or expected.shebang:
|
||||||
|
f.seek(0)
|
||||||
|
f.truncate()
|
||||||
|
f.write(b'')
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if expected.is_expected_pragma(remove):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Otherwise, write out the new file
|
||||||
|
f.seek(0)
|
||||||
|
f.truncate()
|
||||||
|
f.write(expected.shebang)
|
||||||
|
if not remove:
|
||||||
|
f.write(expected_pragma + expected.ending)
|
||||||
|
f.write(expected.rest)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_pragma(pragma): # type: (Union[bytes, str]) -> bytes
|
||||||
|
if not isinstance(pragma, bytes):
|
||||||
|
pragma = pragma.encode('UTF-8')
|
||||||
|
return pragma.rstrip()
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
'Fixes the encoding pragma of python files',
|
||||||
|
)
|
||||||
|
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||||
|
parser.add_argument(
|
||||||
|
'--pragma', default=DEFAULT_PRAGMA, type=_normalize_pragma,
|
||||||
|
help='The encoding pragma to use. Default: {}'.format(
|
||||||
|
DEFAULT_PRAGMA.decode(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--remove', action='store_true',
|
||||||
|
help='Remove the encoding pragma (Useful in a python3-only codebase)',
|
||||||
|
)
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
retv = 0
|
||||||
|
|
||||||
|
if args.remove:
|
||||||
|
fmt = 'Removed encoding pragma from {filename}'
|
||||||
|
else:
|
||||||
|
fmt = 'Added `{pragma}` to {filename}'
|
||||||
|
|
||||||
|
for filename in args.filenames:
|
||||||
|
with open(filename, 'r+b') as f:
|
||||||
|
file_ret = fix_encoding_pragma(
|
||||||
|
f, remove=args.remove, expected_pragma=args.pragma,
|
||||||
|
)
|
||||||
|
retv |= file_ret
|
||||||
|
if file_ret:
|
||||||
|
print(
|
||||||
|
fmt.format(
|
||||||
|
pragma=args.pragma.decode(), filename=filename,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return retv
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
exit(main())
|
||||||
|
|
@ -1,37 +1,25 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
from typing import Optional
|
||||||
import os
|
from typing import Sequence
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
from pre_commit_hooks.util import cmd_output
|
from pre_commit_hooks.util import cmd_output
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
# `argv` is ignored, pre-commit will send us a list of files that we
|
||||||
parser.add_argument('filenames', nargs='*')
|
# don't care about
|
||||||
args = parser.parse_args(argv)
|
|
||||||
|
|
||||||
if (
|
|
||||||
'PRE_COMMIT_FROM_REF' in os.environ and
|
|
||||||
'PRE_COMMIT_TO_REF' in os.environ
|
|
||||||
):
|
|
||||||
diff_arg = '...'.join((
|
|
||||||
os.environ['PRE_COMMIT_FROM_REF'],
|
|
||||||
os.environ['PRE_COMMIT_TO_REF'],
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
diff_arg = '--staged'
|
|
||||||
added_diff = cmd_output(
|
added_diff = cmd_output(
|
||||||
'git', 'diff', '--diff-filter=A', '--raw', diff_arg, '--',
|
'git', 'diff', '--staged', '--diff-filter=A', '--raw',
|
||||||
*args.filenames,
|
|
||||||
)
|
)
|
||||||
retv = 0
|
retv = 0
|
||||||
for line in added_diff.splitlines():
|
for line in added_diff.splitlines():
|
||||||
metadata, filename = line.split('\t', 1)
|
metadata, filename = line.split('\t', 1)
|
||||||
new_mode = metadata.split(' ')[1]
|
new_mode = metadata.split(' ')[1]
|
||||||
if new_mode == '160000':
|
if new_mode == '160000':
|
||||||
print(f'{filename}: new submodule introduced')
|
print('{}: new submodule introduced'.format(filename))
|
||||||
retv = 1
|
retv = 1
|
||||||
|
|
||||||
if retv:
|
if retv:
|
||||||
|
|
@ -45,4 +33,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import collections
|
import collections
|
||||||
from collections.abc import Sequence
|
from typing import Dict
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
CRLF = b'\r\n'
|
CRLF = b'\r\n'
|
||||||
|
|
@ -13,7 +17,7 @@ ALL_ENDINGS = (CR, CRLF, LF)
|
||||||
FIX_TO_LINE_ENDING = {'cr': CR, 'crlf': CRLF, 'lf': LF}
|
FIX_TO_LINE_ENDING = {'cr': CR, 'crlf': CRLF, 'lf': LF}
|
||||||
|
|
||||||
|
|
||||||
def _fix(filename: str, contents: bytes, ending: bytes) -> None:
|
def _fix(filename, contents, ending): # type: (str, bytes, bytes) -> None
|
||||||
new_contents = b''.join(
|
new_contents = b''.join(
|
||||||
line.rstrip(b'\r\n') + ending for line in contents.splitlines(True)
|
line.rstrip(b'\r\n') + ending for line in contents.splitlines(True)
|
||||||
)
|
)
|
||||||
|
|
@ -21,11 +25,11 @@ def _fix(filename: str, contents: bytes, ending: bytes) -> None:
|
||||||
f.write(new_contents)
|
f.write(new_contents)
|
||||||
|
|
||||||
|
|
||||||
def fix_filename(filename: str, fix: str) -> int:
|
def fix_filename(filename, fix): # type: (str, str) -> int
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
contents = f.read()
|
contents = f.read()
|
||||||
|
|
||||||
counts: dict[bytes, int] = collections.defaultdict(int)
|
counts = collections.defaultdict(int) # type: Dict[bytes, int]
|
||||||
|
|
||||||
for line in contents.splitlines(True):
|
for line in contents.splitlines(True):
|
||||||
for ending in ALL_ENDINGS:
|
for ending in ALL_ENDINGS:
|
||||||
|
|
@ -62,7 +66,7 @@ def fix_filename(filename: str, fix: str) -> int:
|
||||||
return other_endings
|
return other_endings
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--fix',
|
'-f', '--fix',
|
||||||
|
|
@ -77,12 +81,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
if fix_filename(filename, args.fix):
|
if fix_filename(filename, args.fix):
|
||||||
if args.fix == 'no':
|
if args.fix == 'no':
|
||||||
print(f'{filename}: mixed line endings')
|
print('{}: mixed line endings'.format(filename))
|
||||||
else:
|
else:
|
||||||
print(f'{filename}: fixed mixed line endings')
|
print('{}: fixed mixed line endings'.format(filename))
|
||||||
retv = 1
|
retv = 1
|
||||||
return retv
|
return retv
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import AbstractSet
|
from typing import AbstractSet
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
from pre_commit_hooks.util import CalledProcessError
|
from pre_commit_hooks.util import CalledProcessError
|
||||||
from pre_commit_hooks.util import cmd_output
|
from pre_commit_hooks.util import cmd_output
|
||||||
|
|
||||||
|
|
||||||
def is_on_branch(
|
def is_on_branch(protected, patterns=frozenset()):
|
||||||
protected: AbstractSet[str],
|
# type: (AbstractSet[str], AbstractSet[str]) -> bool
|
||||||
patterns: AbstractSet[str] = frozenset(),
|
|
||||||
) -> bool:
|
|
||||||
try:
|
try:
|
||||||
ref_name = cmd_output('git', 'symbolic-ref', 'HEAD')
|
ref_name = cmd_output('git', 'symbolic-ref', 'HEAD')
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
|
|
@ -24,7 +23,7 @@ def is_on_branch(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--branch', action='append',
|
'-b', '--branch', action='append',
|
||||||
|
|
@ -39,10 +38,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
)
|
)
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
protected = frozenset(args.branch or ('master', 'main'))
|
protected = frozenset(args.branch or ('master',))
|
||||||
patterns = frozenset(args.pattern or ())
|
patterns = frozenset(args.pattern or ())
|
||||||
return int(is_on_branch(protected, patterns))
|
return int(is_on_branch(protected, patterns))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,50 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Mapping
|
from collections import OrderedDict
|
||||||
from collections.abc import Sequence
|
|
||||||
from difflib import unified_diff
|
from difflib import unified_diff
|
||||||
|
from typing import List
|
||||||
|
from typing import Mapping
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
from typing import Tuple
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from six import text_type
|
||||||
|
|
||||||
|
|
||||||
def _get_pretty_format(
|
def _get_pretty_format(
|
||||||
contents: str,
|
contents, indent, ensure_ascii=True, sort_keys=True, top_keys=(),
|
||||||
indent: str,
|
): # type: (str, str, bool, bool, Sequence[str]) -> str
|
||||||
ensure_ascii: bool = True,
|
def pairs_first(pairs):
|
||||||
sort_keys: bool = True,
|
# type: (Sequence[Tuple[str, str]]) -> Mapping[str, str]
|
||||||
top_keys: Sequence[str] = (),
|
|
||||||
) -> str:
|
|
||||||
def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[str, str]:
|
|
||||||
before = [pair for pair in pairs if pair[0] in top_keys]
|
before = [pair for pair in pairs if pair[0] in top_keys]
|
||||||
before = sorted(before, key=lambda x: top_keys.index(x[0]))
|
before = sorted(before, key=lambda x: top_keys.index(x[0]))
|
||||||
after = [pair for pair in pairs if pair[0] not in top_keys]
|
after = [pair for pair in pairs if pair[0] not in top_keys]
|
||||||
if sort_keys:
|
if sort_keys:
|
||||||
after.sort()
|
after = sorted(after, key=lambda x: x[0])
|
||||||
return dict(before + after)
|
return OrderedDict(before + after)
|
||||||
json_pretty = json.dumps(
|
json_pretty = json.dumps(
|
||||||
json.loads(contents, object_pairs_hook=pairs_first),
|
json.loads(contents, object_pairs_hook=pairs_first),
|
||||||
indent=indent,
|
indent=indent,
|
||||||
ensure_ascii=ensure_ascii,
|
ensure_ascii=ensure_ascii,
|
||||||
|
# Workaround for https://bugs.python.org/issue16333
|
||||||
|
separators=(',', ': '),
|
||||||
)
|
)
|
||||||
return f'{json_pretty}\n'
|
# Ensure unicode (Py2) and add the newline that dumps does not end with.
|
||||||
|
return text_type(json_pretty) + '\n'
|
||||||
|
|
||||||
|
|
||||||
def _autofix(filename: str, new_contents: str) -> None:
|
def _autofix(filename, new_contents): # type: (str, str) -> None
|
||||||
print(f'Fixing file {filename}')
|
print('Fixing file {}'.format(filename))
|
||||||
with open(filename, 'w', encoding='UTF-8') as f:
|
with io.open(filename, 'w', encoding='UTF-8') as f:
|
||||||
f.write(new_contents)
|
f.write(new_contents)
|
||||||
|
|
||||||
|
|
||||||
def parse_num_to_int(s: str) -> int | str:
|
def parse_num_to_int(s): # type: (str) -> Union[int, str]
|
||||||
"""Convert string numbers to int, leaving strings as is."""
|
"""Convert string numbers to int, leaving strings as is."""
|
||||||
try:
|
try:
|
||||||
return int(s)
|
return int(s)
|
||||||
|
|
@ -44,18 +52,18 @@ def parse_num_to_int(s: str) -> int | str:
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
def parse_topkeys(s: str) -> list[str]:
|
def parse_topkeys(s): # type: (str) -> List[str]
|
||||||
return s.split(',')
|
return s.split(',')
|
||||||
|
|
||||||
|
|
||||||
def get_diff(source: str, target: str, file: str) -> str:
|
def get_diff(source, target, file): # type: (str, str, str) -> str
|
||||||
source_lines = source.splitlines(True)
|
source_lines = source.splitlines(True)
|
||||||
target_lines = target.splitlines(True)
|
target_lines = target.splitlines(True)
|
||||||
diff = unified_diff(source_lines, target_lines, fromfile=file, tofile=file)
|
diff = unified_diff(source_lines, target_lines, fromfile=file, tofile=file)
|
||||||
return ''.join(diff)
|
return ''.join(diff)
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--autofix',
|
'--autofix',
|
||||||
|
|
@ -102,7 +110,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
status = 0
|
status = 0
|
||||||
|
|
||||||
for json_file in args.filenames:
|
for json_file in args.filenames:
|
||||||
with open(json_file, encoding='UTF-8') as f:
|
with io.open(json_file, encoding='UTF-8') as f:
|
||||||
contents = f.read()
|
contents = f.read()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -110,28 +118,26 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
contents, args.indent, ensure_ascii=not args.no_ensure_ascii,
|
contents, args.indent, ensure_ascii=not args.no_ensure_ascii,
|
||||||
sort_keys=not args.no_sort_keys, top_keys=args.top_keys,
|
sort_keys=not args.no_sort_keys, top_keys=args.top_keys,
|
||||||
)
|
)
|
||||||
except ValueError:
|
|
||||||
print(
|
|
||||||
f'Input File {json_file} is not a valid JSON, consider using '
|
|
||||||
f'check-json',
|
|
||||||
)
|
|
||||||
status = 1
|
|
||||||
else:
|
|
||||||
if contents != pretty_contents:
|
if contents != pretty_contents:
|
||||||
if args.autofix:
|
if args.autofix:
|
||||||
_autofix(json_file, pretty_contents)
|
_autofix(json_file, pretty_contents)
|
||||||
else:
|
else:
|
||||||
diff_output = get_diff(
|
print(
|
||||||
contents,
|
get_diff(contents, pretty_contents, json_file),
|
||||||
pretty_contents,
|
end='',
|
||||||
json_file,
|
|
||||||
)
|
)
|
||||||
sys.stdout.buffer.write(diff_output.encode())
|
|
||||||
|
|
||||||
status = 1
|
status = 1
|
||||||
|
except ValueError:
|
||||||
|
print(
|
||||||
|
'Input File {} is not a valid JSON, consider using check-json'
|
||||||
|
.format(json_file),
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
|
||||||
argv = argv if argv is not None else sys.argv[1:]
|
|
||||||
hookid, new_hookid, url = argv[:3]
|
|
||||||
raise SystemExit(
|
|
||||||
f'`{hookid}` has been removed -- use `{new_hookid}` from {url}',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
raise SystemExit(main())
|
|
||||||
|
|
@ -1,42 +1,33 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import re
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import IO
|
from typing import IO
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
PASS = 0
|
PASS = 0
|
||||||
FAIL = 1
|
FAIL = 1
|
||||||
|
|
||||||
|
|
||||||
class Requirement:
|
class Requirement(object):
|
||||||
UNTIL_COMPARISON = re.compile(b'={2,3}|!=|~=|>=?|<=?')
|
|
||||||
UNTIL_SEP = re.compile(rb'[^;\s]+')
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self): # type: () -> None
|
||||||
self.value: bytes | None = None
|
super(Requirement, self).__init__()
|
||||||
self.comments: list[bytes] = []
|
self.value = None # type: Optional[bytes]
|
||||||
|
self.comments = [] # type: List[bytes]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> bytes:
|
def name(self): # type: () -> bytes
|
||||||
assert self.value is not None, self.value
|
assert self.value is not None, self.value
|
||||||
name = self.value.lower()
|
|
||||||
for egg in (b'#egg=', b'&egg='):
|
for egg in (b'#egg=', b'&egg='):
|
||||||
if egg in self.value:
|
if egg in self.value:
|
||||||
return name.partition(egg)[-1]
|
return self.value.lower().partition(egg)[-1]
|
||||||
|
|
||||||
m = self.UNTIL_SEP.match(name)
|
return self.value.lower().partition(b'==')[0]
|
||||||
assert m is not None
|
|
||||||
|
|
||||||
name = m.group()
|
def __lt__(self, requirement): # type: (Requirement) -> int
|
||||||
m = self.UNTIL_COMPARISON.search(name)
|
|
||||||
if not m:
|
|
||||||
return name
|
|
||||||
|
|
||||||
return name[:m.start()]
|
|
||||||
|
|
||||||
def __lt__(self, requirement: Requirement) -> bool:
|
|
||||||
# \n means top of file comment, so always return True,
|
# \n means top of file comment, so always return True,
|
||||||
# otherwise just do a string comparison with value.
|
# otherwise just do a string comparison with value.
|
||||||
assert self.value is not None, self.value
|
assert self.value is not None, self.value
|
||||||
|
|
@ -45,30 +36,13 @@ class Requirement:
|
||||||
elif requirement.value == b'\n':
|
elif requirement.value == b'\n':
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
# if 2 requirements have the same name, the one with comments
|
|
||||||
# needs to go first (so that when removing duplicates, the one
|
|
||||||
# with comments is kept)
|
|
||||||
if self.name == requirement.name:
|
|
||||||
return bool(self.comments) > bool(requirement.comments)
|
|
||||||
return self.name < requirement.name
|
return self.name < requirement.name
|
||||||
|
|
||||||
def is_complete(self) -> bool:
|
|
||||||
return (
|
|
||||||
self.value is not None and
|
|
||||||
not self.value.rstrip(b'\r\n').endswith(b'\\')
|
|
||||||
)
|
|
||||||
|
|
||||||
def append_value(self, value: bytes) -> None:
|
def fix_requirements(f): # type: (IO[bytes]) -> int
|
||||||
if self.value is not None:
|
requirements = [] # type: List[Requirement]
|
||||||
self.value += value
|
|
||||||
else:
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
def fix_requirements(f: IO[bytes]) -> int:
|
|
||||||
requirements: list[Requirement] = []
|
|
||||||
before = list(f)
|
before = list(f)
|
||||||
after: list[bytes] = []
|
after = [] # type: List[bytes]
|
||||||
|
|
||||||
before_string = b''.join(before)
|
before_string = b''.join(before)
|
||||||
|
|
||||||
|
|
@ -85,7 +59,7 @@ def fix_requirements(f: IO[bytes]) -> int:
|
||||||
# If the most recent requirement object has a value, then it's
|
# If the most recent requirement object has a value, then it's
|
||||||
# time to start building the next requirement object.
|
# time to start building the next requirement object.
|
||||||
|
|
||||||
if not len(requirements) or requirements[-1].is_complete():
|
if not len(requirements) or requirements[-1].value is not None:
|
||||||
requirements.append(Requirement())
|
requirements.append(Requirement())
|
||||||
|
|
||||||
requirement = requirements[-1]
|
requirement = requirements[-1]
|
||||||
|
|
@ -100,10 +74,10 @@ def fix_requirements(f: IO[bytes]) -> int:
|
||||||
requirement.value = b'\n'
|
requirement.value = b'\n'
|
||||||
else:
|
else:
|
||||||
requirement.comments.append(line)
|
requirement.comments.append(line)
|
||||||
elif line.lstrip().startswith(b'#') or line.strip() == b'':
|
elif line.startswith(b'#') or line.strip() == b'':
|
||||||
requirement.comments.append(line)
|
requirement.comments.append(line)
|
||||||
else:
|
else:
|
||||||
requirement.append_value(line)
|
requirement.value = line
|
||||||
|
|
||||||
# if a file ends in a comment, preserve it at the end
|
# if a file ends in a comment, preserve it at the end
|
||||||
if requirements[-1].value is None:
|
if requirements[-1].value is None:
|
||||||
|
|
@ -115,20 +89,13 @@ def fix_requirements(f: IO[bytes]) -> int:
|
||||||
# which is automatically added by broken pip package under Debian
|
# which is automatically added by broken pip package under Debian
|
||||||
requirements = [
|
requirements = [
|
||||||
req for req in requirements
|
req for req in requirements
|
||||||
if req.value not in [
|
if req.value != b'pkg-resources==0.0.0\n'
|
||||||
b'pkg-resources==0.0.0\n',
|
|
||||||
b'pkg_resources==0.0.0\n',
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# sort the requirements and remove duplicates
|
|
||||||
prev = None
|
|
||||||
for requirement in sorted(requirements):
|
for requirement in sorted(requirements):
|
||||||
after.extend(requirement.comments)
|
after.extend(requirement.comments)
|
||||||
assert requirement.value, requirement.value
|
assert requirement.value, requirement.value
|
||||||
if prev is None or requirement.value != prev.value:
|
|
||||||
after.append(requirement.value)
|
after.append(requirement.value)
|
||||||
prev = requirement
|
|
||||||
after.extend(rest)
|
after.extend(rest)
|
||||||
|
|
||||||
after_string = b''.join(after)
|
after_string = b''.join(after)
|
||||||
|
|
@ -142,7 +109,7 @@ def fix_requirements(f: IO[bytes]) -> int:
|
||||||
return FAIL
|
return FAIL
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
@ -154,7 +121,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
ret_for_file = fix_requirements(file_obj)
|
ret_for_file = fix_requirements(file_obj)
|
||||||
|
|
||||||
if ret_for_file:
|
if ret_for_file:
|
||||||
print(f'Sorting {arg}')
|
print('Sorting {}'.format(arg))
|
||||||
|
|
||||||
retv |= ret_for_file
|
retv |= ret_for_file
|
||||||
|
|
||||||
|
|
@ -162,4 +129,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
21
pre_commit_hooks/sort_simple_yaml.py
Normal file → Executable file
21
pre_commit_hooks/sort_simple_yaml.py
Normal file → Executable file
|
|
@ -1,3 +1,4 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
"""Sort a simple YAML file, keeping blocks of comments and definitions
|
"""Sort a simple YAML file, keeping blocks of comments and definitions
|
||||||
together.
|
together.
|
||||||
|
|
||||||
|
|
@ -17,16 +18,18 @@ We assume a strict subset of YAML that looks like:
|
||||||
In other words, we don't sort deeper than the top layer, and might corrupt
|
In other words, we don't sort deeper than the top layer, and might corrupt
|
||||||
complicated YAML files.
|
complicated YAML files.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from collections.abc import Sequence
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
QUOTES = ["'", '"']
|
QUOTES = ["'", '"']
|
||||||
|
|
||||||
|
|
||||||
def sort(lines: list[str]) -> list[str]:
|
def sort(lines): # type: (List[str]) -> List[str]
|
||||||
"""Sort a YAML file in alphabetical order, keeping blocks together.
|
"""Sort a YAML file in alphabetical order, keeping blocks together.
|
||||||
|
|
||||||
:param lines: array of strings (without newlines)
|
:param lines: array of strings (without newlines)
|
||||||
|
|
@ -44,7 +47,7 @@ def sort(lines: list[str]) -> list[str]:
|
||||||
return new_lines
|
return new_lines
|
||||||
|
|
||||||
|
|
||||||
def parse_block(lines: list[str], header: bool = False) -> list[str]:
|
def parse_block(lines, header=False): # type: (List[str], bool) -> List[str]
|
||||||
"""Parse and return a single block, popping off the start of `lines`.
|
"""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
|
If parsing a header block, we stop after we reach a line that is not a
|
||||||
|
|
@ -60,7 +63,7 @@ def parse_block(lines: list[str], header: bool = False) -> list[str]:
|
||||||
return block_lines
|
return block_lines
|
||||||
|
|
||||||
|
|
||||||
def parse_blocks(lines: list[str]) -> list[list[str]]:
|
def parse_blocks(lines): # type: (List[str]) -> List[List[str]]
|
||||||
"""Parse and return all possible blocks, popping off the start of `lines`.
|
"""Parse and return all possible blocks, popping off the start of `lines`.
|
||||||
|
|
||||||
:param lines: list of lines
|
:param lines: list of lines
|
||||||
|
|
@ -77,7 +80,7 @@ def parse_blocks(lines: list[str]) -> list[list[str]]:
|
||||||
return blocks
|
return blocks
|
||||||
|
|
||||||
|
|
||||||
def first_key(lines: list[str]) -> str:
|
def first_key(lines): # type: (List[str]) -> str
|
||||||
"""Returns a string representing the sort key of a block.
|
"""Returns a string representing the sort key of a block.
|
||||||
|
|
||||||
The sort key is the first YAML key we encounter, ignoring comments, and
|
The sort key is the first YAML key we encounter, ignoring comments, and
|
||||||
|
|
@ -99,7 +102,7 @@ def first_key(lines: list[str]) -> str:
|
||||||
return '' # not actually reached in reality
|
return '' # not actually reached in reality
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
@ -112,7 +115,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
new_lines = sort(lines)
|
new_lines = sort(lines)
|
||||||
|
|
||||||
if lines != new_lines:
|
if lines != new_lines:
|
||||||
print(f'Fixing file `{filename}`')
|
print('Fixing file `{filename}`'.format(filename=filename))
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
f.write('\n'.join(new_lines) + '\n')
|
f.write('\n'.join(new_lines) + '\n')
|
||||||
f.truncate()
|
f.truncate()
|
||||||
|
|
@ -122,4 +125,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,19 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import tokenize
|
import tokenize
|
||||||
from collections.abc import Sequence
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
if sys.version_info >= (3, 12): # pragma: >=3.12 cover
|
from typing import Sequence
|
||||||
FSTRING_START = tokenize.FSTRING_START
|
|
||||||
FSTRING_END = tokenize.FSTRING_END
|
|
||||||
else: # pragma: <3.12 cover
|
|
||||||
FSTRING_START = FSTRING_END = -1
|
|
||||||
|
|
||||||
START_QUOTE_RE = re.compile('^[a-zA-Z]*"')
|
START_QUOTE_RE = re.compile('^[a-zA-Z]*"')
|
||||||
|
|
||||||
|
|
||||||
def handle_match(token_text: str) -> str:
|
def handle_match(token_text): # type: (str) -> str
|
||||||
if '"""' in token_text or "'''" in token_text:
|
if '"""' in token_text or "'''" in token_text:
|
||||||
return token_text
|
return token_text
|
||||||
|
|
||||||
|
|
@ -31,7 +28,7 @@ def handle_match(token_text: str) -> str:
|
||||||
return token_text
|
return token_text
|
||||||
|
|
||||||
|
|
||||||
def get_line_offsets_by_line_no(src: str) -> list[int]:
|
def get_line_offsets_by_line_no(src): # type: (str) -> List[int]
|
||||||
# Padded so we can index with line number
|
# Padded so we can index with line number
|
||||||
offsets = [-1, 0]
|
offsets = [-1, 0]
|
||||||
for line in src.splitlines(True):
|
for line in src.splitlines(True):
|
||||||
|
|
@ -39,25 +36,19 @@ def get_line_offsets_by_line_no(src: str) -> list[int]:
|
||||||
return offsets
|
return offsets
|
||||||
|
|
||||||
|
|
||||||
def fix_strings(filename: str) -> int:
|
def fix_strings(filename): # type: (str) -> int
|
||||||
with open(filename, encoding='UTF-8', newline='') as f:
|
with io.open(filename, encoding='UTF-8', newline='') as f:
|
||||||
contents = f.read()
|
contents = f.read()
|
||||||
line_offsets = get_line_offsets_by_line_no(contents)
|
line_offsets = get_line_offsets_by_line_no(contents)
|
||||||
|
|
||||||
# Basically a mutable string
|
# Basically a mutable string
|
||||||
splitcontents = list(contents)
|
splitcontents = list(contents)
|
||||||
|
|
||||||
fstring_depth = 0
|
|
||||||
|
|
||||||
# Iterate in reverse so the offsets are always correct
|
# Iterate in reverse so the offsets are always correct
|
||||||
tokens_l = list(tokenize.generate_tokens(io.StringIO(contents).readline))
|
tokens_l = list(tokenize.generate_tokens(io.StringIO(contents).readline))
|
||||||
tokens = reversed(tokens_l)
|
tokens = reversed(tokens_l)
|
||||||
for token_type, token_text, (srow, scol), (erow, ecol), _ in tokens:
|
for token_type, token_text, (srow, scol), (erow, ecol), _ in tokens:
|
||||||
if token_type == FSTRING_START: # pragma: >=3.12 cover
|
if token_type == tokenize.STRING:
|
||||||
fstring_depth += 1
|
|
||||||
elif token_type == FSTRING_END: # pragma: >=3.12 cover
|
|
||||||
fstring_depth -= 1
|
|
||||||
elif fstring_depth == 0 and token_type == tokenize.STRING:
|
|
||||||
new_text = handle_match(token_text)
|
new_text = handle_match(token_text)
|
||||||
splitcontents[
|
splitcontents[
|
||||||
line_offsets[srow] + scol:
|
line_offsets[srow] + scol:
|
||||||
|
|
@ -66,14 +57,14 @@ def fix_strings(filename: str) -> int:
|
||||||
|
|
||||||
new_contents = ''.join(splitcontents)
|
new_contents = ''.join(splitcontents)
|
||||||
if contents != new_contents:
|
if contents != new_contents:
|
||||||
with open(filename, 'w', encoding='UTF-8', newline='') as f:
|
with io.open(filename, 'w', encoding='UTF-8', newline='') as f:
|
||||||
f.write(new_contents)
|
f.write(new_contents)
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
@ -83,11 +74,11 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
return_value = fix_strings(filename)
|
return_value = fix_strings(filename)
|
||||||
if return_value != 0:
|
if return_value != 0:
|
||||||
print(f'Fixing strings in {filename}')
|
print('Fixing strings in {}'.format(filename))
|
||||||
retv |= return_value
|
retv |= return_value
|
||||||
|
|
||||||
return retv
|
return retv
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,40 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
from collections.abc import Sequence
|
import sys
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('filenames', nargs='*')
|
parser.add_argument('filenames', nargs='*')
|
||||||
mutex = parser.add_mutually_exclusive_group()
|
parser.add_argument(
|
||||||
mutex.add_argument(
|
'--django', default=False, action='store_true',
|
||||||
'--pytest',
|
help='Use Django-style test naming pattern (test*.py)',
|
||||||
dest='pattern',
|
|
||||||
action='store_const',
|
|
||||||
const=r'.*_test\.py',
|
|
||||||
default=r'.*_test\.py',
|
|
||||||
help='(the default) ensure tests match %(const)s',
|
|
||||||
)
|
|
||||||
mutex.add_argument(
|
|
||||||
'--pytest-test-first',
|
|
||||||
dest='pattern',
|
|
||||||
action='store_const',
|
|
||||||
const=r'test_.*\.py',
|
|
||||||
help='ensure tests match %(const)s',
|
|
||||||
)
|
|
||||||
mutex.add_argument(
|
|
||||||
'--django', '--unittest',
|
|
||||||
dest='pattern',
|
|
||||||
action='store_const',
|
|
||||||
const=r'test.*\.py',
|
|
||||||
help='ensure tests match %(const)s',
|
|
||||||
)
|
)
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
retcode = 0
|
retcode = 0
|
||||||
reg = re.compile(args.pattern)
|
test_name_pattern = r'test.*\.py' if args.django else r'.*_test\.py'
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
base = os.path.basename(filename)
|
base = os.path.basename(filename)
|
||||||
if (
|
if (
|
||||||
not reg.fullmatch(base) and
|
not re.match(test_name_pattern, base) and
|
||||||
not base == '__init__.py' and
|
not base == '__init__.py' and
|
||||||
not base == 'conftest.py'
|
not base == 'conftest.py'
|
||||||
):
|
):
|
||||||
retcode = 1
|
retcode = 1
|
||||||
print(f'{filename} does not match pattern "{args.pattern}"')
|
print(
|
||||||
|
'{} does not match pattern "{}"'.format(
|
||||||
|
filename, test_name_pattern,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return retcode
|
return retcode
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
from __future__ import annotations
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
from collections.abc import Sequence
|
import sys
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
def _fix_file(
|
def _fix_file(filename, is_markdown, chars):
|
||||||
filename: str,
|
# type: (str, bool, Optional[bytes]) -> bool
|
||||||
is_markdown: bool,
|
|
||||||
chars: bytes | None,
|
|
||||||
) -> bool:
|
|
||||||
with open(filename, mode='rb') as file_processed:
|
with open(filename, mode='rb') as file_processed:
|
||||||
lines = file_processed.readlines()
|
lines = file_processed.readlines()
|
||||||
newlines = [_process_line(line, is_markdown, chars) for line in lines]
|
newlines = [_process_line(line, is_markdown, chars) for line in lines]
|
||||||
|
|
@ -22,11 +21,8 @@ def _fix_file(
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _process_line(
|
def _process_line(line, is_markdown, chars):
|
||||||
line: bytes,
|
# type: (bytes, bool, Optional[bytes]) -> bytes
|
||||||
is_markdown: bool,
|
|
||||||
chars: bytes | None,
|
|
||||||
) -> bytes:
|
|
||||||
if line[-2:] == b'\r\n':
|
if line[-2:] == b'\r\n':
|
||||||
eol = b'\r\n'
|
eol = b'\r\n'
|
||||||
line = line[:-2]
|
line = line[:-2]
|
||||||
|
|
@ -41,7 +37,7 @@ def _process_line(
|
||||||
return line.rstrip(chars) + eol
|
return line.rstrip(chars) + eol
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv=None): # type: (Optional[Sequence[str]]) -> int
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--no-markdown-linebreak-ext',
|
'--no-markdown-linebreak-ext',
|
||||||
|
|
@ -84,20 +80,20 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
for ext in md_exts:
|
for ext in md_exts:
|
||||||
if any(c in ext[1:] for c in r'./\:'):
|
if any(c in ext[1:] for c in r'./\:'):
|
||||||
parser.error(
|
parser.error(
|
||||||
f'bad --markdown-linebreak-ext extension '
|
'bad --markdown-linebreak-ext extension {!r} (has . / \\ :)\n'
|
||||||
f'{ext!r} (has . / \\ :)\n'
|
" (probably filename; use '--markdown-linebreak-ext=EXT')"
|
||||||
f" (probably filename; use '--markdown-linebreak-ext=EXT')",
|
.format(ext),
|
||||||
)
|
)
|
||||||
chars = None if args.chars is None else args.chars.encode()
|
chars = None if args.chars is None else args.chars.encode('utf-8')
|
||||||
return_code = 0
|
return_code = 0
|
||||||
for filename in args.filenames:
|
for filename in args.filenames:
|
||||||
_, extension = os.path.splitext(filename.lower())
|
_, extension = os.path.splitext(filename.lower())
|
||||||
md = all_markdown or extension in md_exts
|
md = all_markdown or extension in md_exts
|
||||||
if _fix_file(filename, md, chars):
|
if _fix_file(filename, md, chars):
|
||||||
print(f'Fixing {filename}')
|
print('Fixing {}'.format(filename))
|
||||||
return_code = 1
|
return_code = 1
|
||||||
return return_code
|
return return_code
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
sys.exit(main())
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,28 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
|
||||||
class CalledProcessError(RuntimeError):
|
class CalledProcessError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def added_files() -> set[str]:
|
def added_files(): # type: () -> Set[str]
|
||||||
cmd = ('git', 'diff', '--staged', '--name-only', '--diff-filter=A')
|
cmd = ('git', 'diff', '--staged', '--name-only', '--diff-filter=A')
|
||||||
return set(cmd_output(*cmd).splitlines())
|
return set(cmd_output(*cmd).splitlines())
|
||||||
|
|
||||||
|
|
||||||
def cmd_output(*cmd: str, retcode: int | None = 0, **kwargs: Any) -> str:
|
def cmd_output(*cmd, **kwargs): # type: (*str, **Any) -> str
|
||||||
|
retcode = kwargs.pop('retcode', 0)
|
||||||
kwargs.setdefault('stdout', subprocess.PIPE)
|
kwargs.setdefault('stdout', subprocess.PIPE)
|
||||||
kwargs.setdefault('stderr', subprocess.PIPE)
|
kwargs.setdefault('stderr', subprocess.PIPE)
|
||||||
proc = subprocess.Popen(cmd, **kwargs)
|
proc = subprocess.Popen(cmd, **kwargs)
|
||||||
stdout, stderr = proc.communicate()
|
stdout, stderr = proc.communicate()
|
||||||
stdout = stdout.decode()
|
stdout = stdout.decode('UTF-8')
|
||||||
if retcode is not None and proc.returncode != retcode:
|
if retcode is not None and proc.returncode != retcode:
|
||||||
raise CalledProcessError(cmd, retcode, proc.returncode, stdout, stderr)
|
raise CalledProcessError(cmd, retcode, proc.returncode, stdout, stderr)
|
||||||
return stdout
|
return stdout
|
||||||
|
|
||||||
|
|
||||||
def zsplit(s: str) -> list[str]:
|
|
||||||
s = s.strip('\0')
|
|
||||||
if s:
|
|
||||||
return s.split('\0')
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
covdefaults
|
-e .
|
||||||
|
|
||||||
coverage
|
coverage
|
||||||
|
mock
|
||||||
|
pre-commit
|
||||||
pytest
|
pytest
|
||||||
|
|
|
||||||
42
setup.cfg
42
setup.cfg
|
|
@ -1,6 +1,6 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = pre_commit_hooks
|
name = pre_commit_hooks
|
||||||
version = 6.0.0
|
version = 2.5.0
|
||||||
description = Some out-of-the-box hooks for pre-commit.
|
description = Some out-of-the-box hooks for pre-commit.
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long_description_content_type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
|
|
@ -8,72 +8,76 @@ url = https://github.com/pre-commit/pre-commit-hooks
|
||||||
author = Anthony Sottile
|
author = Anthony Sottile
|
||||||
author_email = asottile@umich.edu
|
author_email = asottile@umich.edu
|
||||||
license = MIT
|
license = MIT
|
||||||
license_files = LICENSE
|
license_file = LICENSE
|
||||||
classifiers =
|
classifiers =
|
||||||
|
License :: OSI Approved :: MIT License
|
||||||
|
Programming Language :: Python :: 2
|
||||||
|
Programming Language :: Python :: 2.7
|
||||||
Programming Language :: Python :: 3
|
Programming Language :: Python :: 3
|
||||||
Programming Language :: Python :: 3 :: Only
|
Programming Language :: Python :: 3.4
|
||||||
|
Programming Language :: Python :: 3.5
|
||||||
|
Programming Language :: Python :: 3.6
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
Programming Language :: Python :: Implementation :: CPython
|
Programming Language :: Python :: Implementation :: CPython
|
||||||
Programming Language :: Python :: Implementation :: PyPy
|
Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
packages = find:
|
packages = find:
|
||||||
install_requires =
|
install_requires =
|
||||||
|
flake8
|
||||||
ruamel.yaml>=0.15
|
ruamel.yaml>=0.15
|
||||||
tomli>=1.1.0;python_version<"3.11"
|
toml
|
||||||
python_requires = >=3.10
|
six
|
||||||
|
typing; python_version<"3.5"
|
||||||
[options.packages.find]
|
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||||
exclude =
|
|
||||||
tests*
|
|
||||||
testing*
|
|
||||||
|
|
||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
|
autopep8-wrapper = pre_commit_hooks.autopep8_wrapper:main
|
||||||
check-added-large-files = pre_commit_hooks.check_added_large_files:main
|
check-added-large-files = pre_commit_hooks.check_added_large_files:main
|
||||||
check-ast = pre_commit_hooks.check_ast:main
|
check-ast = pre_commit_hooks.check_ast:main
|
||||||
check-builtin-literals = pre_commit_hooks.check_builtin_literals:main
|
check-builtin-literals = pre_commit_hooks.check_builtin_literals:main
|
||||||
|
check-byte-order-marker = pre_commit_hooks.check_byte_order_marker:main
|
||||||
check-case-conflict = pre_commit_hooks.check_case_conflict:main
|
check-case-conflict = pre_commit_hooks.check_case_conflict:main
|
||||||
check-docstring-first = pre_commit_hooks.check_docstring_first:main
|
check-docstring-first = pre_commit_hooks.check_docstring_first:main
|
||||||
check-executables-have-shebangs = pre_commit_hooks.check_executables_have_shebangs:main
|
check-executables-have-shebangs = pre_commit_hooks.check_executables_have_shebangs:main
|
||||||
check-json = pre_commit_hooks.check_json:main
|
check-json = pre_commit_hooks.check_json:main
|
||||||
check-merge-conflict = pre_commit_hooks.check_merge_conflict:main
|
check-merge-conflict = pre_commit_hooks.check_merge_conflict:main
|
||||||
check-shebang-scripts-are-executable = pre_commit_hooks.check_shebang_scripts_are_executable:main
|
|
||||||
check-symlinks = pre_commit_hooks.check_symlinks:main
|
check-symlinks = pre_commit_hooks.check_symlinks:main
|
||||||
check-toml = pre_commit_hooks.check_toml:main
|
check-toml = pre_commit_hooks.check_toml:main
|
||||||
check-vcs-permalinks = pre_commit_hooks.check_vcs_permalinks:main
|
check-vcs-permalinks = pre_commit_hooks.check_vcs_permalinks:main
|
||||||
check-xml = pre_commit_hooks.check_xml:main
|
check-xml = pre_commit_hooks.check_xml:main
|
||||||
check-yaml = pre_commit_hooks.check_yaml:main
|
check-yaml = pre_commit_hooks.check_yaml:main
|
||||||
debug-statement-hook = pre_commit_hooks.debug_statement_hook:main
|
debug-statement-hook = pre_commit_hooks.debug_statement_hook:main
|
||||||
destroyed-symlinks = pre_commit_hooks.destroyed_symlinks:main
|
|
||||||
detect-aws-credentials = pre_commit_hooks.detect_aws_credentials:main
|
detect-aws-credentials = pre_commit_hooks.detect_aws_credentials:main
|
||||||
detect-private-key = pre_commit_hooks.detect_private_key:main
|
detect-private-key = pre_commit_hooks.detect_private_key:main
|
||||||
double-quote-string-fixer = pre_commit_hooks.string_fixer:main
|
double-quote-string-fixer = pre_commit_hooks.string_fixer:main
|
||||||
end-of-file-fixer = pre_commit_hooks.end_of_file_fixer:main
|
end-of-file-fixer = pre_commit_hooks.end_of_file_fixer:main
|
||||||
file-contents-sorter = pre_commit_hooks.file_contents_sorter:main
|
file-contents-sorter = pre_commit_hooks.file_contents_sorter:main
|
||||||
fix-byte-order-marker = pre_commit_hooks.fix_byte_order_marker:main
|
fix-encoding-pragma = pre_commit_hooks.fix_encoding_pragma:main
|
||||||
forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main
|
forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main
|
||||||
mixed-line-ending = pre_commit_hooks.mixed_line_ending:main
|
mixed-line-ending = pre_commit_hooks.mixed_line_ending:main
|
||||||
name-tests-test = pre_commit_hooks.tests_should_end_in_test:main
|
name-tests-test = pre_commit_hooks.tests_should_end_in_test:main
|
||||||
no-commit-to-branch = pre_commit_hooks.no_commit_to_branch:main
|
no-commit-to-branch = pre_commit_hooks.no_commit_to_branch:main
|
||||||
pre-commit-hooks-removed = pre_commit_hooks.removed:main
|
|
||||||
pretty-format-json = pre_commit_hooks.pretty_format_json:main
|
pretty-format-json = pre_commit_hooks.pretty_format_json:main
|
||||||
requirements-txt-fixer = pre_commit_hooks.requirements_txt_fixer:main
|
requirements-txt-fixer = pre_commit_hooks.requirements_txt_fixer:main
|
||||||
sort-simple-yaml = pre_commit_hooks.sort_simple_yaml:main
|
sort-simple-yaml = pre_commit_hooks.sort_simple_yaml:main
|
||||||
trailing-whitespace-fixer = pre_commit_hooks.trailing_whitespace_fixer:main
|
trailing-whitespace-fixer = pre_commit_hooks.trailing_whitespace_fixer:main
|
||||||
|
|
||||||
|
[options.packages.find]
|
||||||
|
exclude =
|
||||||
|
tests*
|
||||||
|
testing*
|
||||||
|
|
||||||
[bdist_wheel]
|
[bdist_wheel]
|
||||||
universal = True
|
universal = True
|
||||||
|
|
||||||
[coverage:run]
|
|
||||||
plugins = covdefaults
|
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_any_generics = true
|
disallow_any_generics = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
disallow_untyped_defs = true
|
disallow_untyped_defs = true
|
||||||
warn_redundant_casts = true
|
no_implicit_optional = true
|
||||||
warn_unused_ignores = true
|
|
||||||
|
|
||||||
[mypy-testing.*]
|
[mypy-testing.*]
|
||||||
disallow_untyped_defs = false
|
disallow_untyped_defs = false
|
||||||
|
|
|
||||||
2
setup.py
2
setup.py
|
|
@ -1,4 +1,2 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
setup()
|
setup()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"hello": "world",
|
|
||||||
"hello": "planet"
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
TESTING_DIR = os.path.abspath(os.path.dirname(__file__))
|
TESTING_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
@ -9,8 +9,3 @@ TESTING_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
def get_resource_path(path):
|
def get_resource_path(path):
|
||||||
return os.path.join(TESTING_DIR, 'resources', path)
|
return os.path.join(TESTING_DIR, 'resources', path)
|
||||||
|
|
||||||
|
|
||||||
def git_commit(*args, **kwargs):
|
|
||||||
cmd = ('git', 'commit', '--no-gpg-sign', '--no-verify', '--no-edit', *args)
|
|
||||||
subprocess.check_call(cmd, **kwargs)
|
|
||||||
|
|
|
||||||
13
tests/autopep8_wrapper_test.py
Normal file
13
tests/autopep8_wrapper_test.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from pre_commit_hooks.autopep8_wrapper import main
|
||||||
|
|
||||||
|
|
||||||
|
def test_invariantly_fails():
|
||||||
|
with pytest.raises(SystemExit) as excinfo:
|
||||||
|
main()
|
||||||
|
msg, = excinfo.value.args
|
||||||
|
assert 'https://github.com/pre-commit/mirrors-autopep8' in msg
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import shutil
|
import distutils.spawn
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks.check_added_large_files import find_large_added_files
|
from pre_commit_hooks.check_added_large_files import find_large_added_files
|
||||||
from pre_commit_hooks.check_added_large_files import main
|
from pre_commit_hooks.check_added_large_files import main
|
||||||
from pre_commit_hooks.util import cmd_output
|
from pre_commit_hooks.util import cmd_output
|
||||||
from testing.util import git_commit
|
|
||||||
|
|
||||||
|
|
||||||
def test_nothing_added(temp_git_dir):
|
def test_nothing_added(temp_git_dir):
|
||||||
|
|
@ -43,17 +43,6 @@ def test_add_something_giant(temp_git_dir):
|
||||||
assert find_large_added_files(['f.py'], 10) == 0
|
assert find_large_added_files(['f.py'], 10) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_enforce_all(temp_git_dir):
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
temp_git_dir.join('f.py').write('a' * 10000)
|
|
||||||
|
|
||||||
# Should fail, when not staged with enforce_all
|
|
||||||
assert find_large_added_files(['f.py'], 0, enforce_all=True) == 1
|
|
||||||
|
|
||||||
# Should pass, when not staged without enforce_all
|
|
||||||
assert find_large_added_files(['f.py'], 0, enforce_all=False) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_added_file_not_in_pre_commits_list(temp_git_dir):
|
def test_added_file_not_in_pre_commits_list(temp_git_dir):
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
temp_git_dir.join('f.py').write("print('hello world')")
|
temp_git_dir.join('f.py').write("print('hello world')")
|
||||||
|
|
@ -78,7 +67,7 @@ def test_integration(temp_git_dir):
|
||||||
|
|
||||||
|
|
||||||
def has_gitlfs():
|
def has_gitlfs():
|
||||||
return shutil.which('git-lfs') is not None
|
return distutils.spawn.find_executable('git-lfs') is not None
|
||||||
|
|
||||||
|
|
||||||
xfailif_no_gitlfs = pytest.mark.xfail(
|
xfailif_no_gitlfs = pytest.mark.xfail(
|
||||||
|
|
@ -87,9 +76,10 @@ xfailif_no_gitlfs = pytest.mark.xfail(
|
||||||
|
|
||||||
|
|
||||||
@xfailif_no_gitlfs
|
@xfailif_no_gitlfs
|
||||||
def test_allows_gitlfs(temp_git_dir): # pragma: no cover
|
def test_allows_gitlfs(temp_git_dir, monkeypatch): # pragma: no cover
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
cmd_output('git', 'lfs', 'install', '--local')
|
monkeypatch.setenv(str('HOME'), str(temp_git_dir.strpath))
|
||||||
|
cmd_output('git', 'lfs', 'install')
|
||||||
temp_git_dir.join('f.py').write('a' * 10000)
|
temp_git_dir.join('f.py').write('a' * 10000)
|
||||||
cmd_output('git', 'lfs', 'track', 'f.py')
|
cmd_output('git', 'lfs', 'track', 'f.py')
|
||||||
cmd_output('git', 'add', '--', '.')
|
cmd_output('git', 'add', '--', '.')
|
||||||
|
|
@ -98,37 +88,15 @@ def test_allows_gitlfs(temp_git_dir): # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
@xfailif_no_gitlfs
|
@xfailif_no_gitlfs
|
||||||
def test_moves_with_gitlfs(temp_git_dir): # pragma: no cover
|
def test_moves_with_gitlfs(temp_git_dir, monkeypatch): # pragma: no cover
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
cmd_output('git', 'lfs', 'install', '--local')
|
monkeypatch.setenv(str('HOME'), str(temp_git_dir.strpath))
|
||||||
|
cmd_output('git', 'lfs', 'install')
|
||||||
cmd_output('git', 'lfs', 'track', 'a.bin', 'b.bin')
|
cmd_output('git', 'lfs', 'track', 'a.bin', 'b.bin')
|
||||||
# First add the file we're going to move
|
# First add the file we're going to move
|
||||||
temp_git_dir.join('a.bin').write('a' * 10000)
|
temp_git_dir.join('a.bin').write('a' * 10000)
|
||||||
cmd_output('git', 'add', '--', '.')
|
cmd_output('git', 'add', '--', '.')
|
||||||
git_commit('-am', 'foo')
|
cmd_output('git', 'commit', '--no-gpg-sign', '-am', 'foo')
|
||||||
# Now move it and make sure the hook still succeeds
|
# Now move it and make sure the hook still succeeds
|
||||||
cmd_output('git', 'mv', 'a.bin', 'b.bin')
|
cmd_output('git', 'mv', 'a.bin', 'b.bin')
|
||||||
assert main(('--maxkb', '9', 'b.bin')) == 0
|
assert main(('--maxkb', '9', 'b.bin')) == 0
|
||||||
|
|
||||||
|
|
||||||
@xfailif_no_gitlfs
|
|
||||||
def test_enforce_allows_gitlfs(temp_git_dir): # pragma: no cover
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
cmd_output('git', 'lfs', 'install', '--local')
|
|
||||||
temp_git_dir.join('f.py').write('a' * 10000)
|
|
||||||
cmd_output('git', 'lfs', 'track', 'f.py')
|
|
||||||
cmd_output('git', 'add', '--', '.')
|
|
||||||
# With --enforce-all large files on git lfs should succeed
|
|
||||||
assert main(('--enforce-all', '--maxkb', '9', 'f.py')) == 0
|
|
||||||
|
|
||||||
|
|
||||||
@xfailif_no_gitlfs
|
|
||||||
def test_enforce_allows_gitlfs_after_commit(temp_git_dir): # pragma: no cover
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
cmd_output('git', 'lfs', 'install', '--local')
|
|
||||||
temp_git_dir.join('f.py').write('a' * 10000)
|
|
||||||
cmd_output('git', 'lfs', 'track', 'f.py')
|
|
||||||
cmd_output('git', 'add', '--', '.')
|
|
||||||
git_commit('-am', 'foo')
|
|
||||||
# With --enforce-all large files on git lfs should succeed
|
|
||||||
assert main(('--enforce-all', '--maxkb', '9', 'f.py')) == 0
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from pre_commit_hooks.check_ast import main
|
from pre_commit_hooks.check_ast import main
|
||||||
from testing.util import get_resource_path
|
from testing.util import get_resource_path
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -9,7 +7,7 @@ from pre_commit_hooks.check_builtin_literals import main
|
||||||
from pre_commit_hooks.check_builtin_literals import Visitor
|
from pre_commit_hooks.check_builtin_literals import Visitor
|
||||||
|
|
||||||
BUILTIN_CONSTRUCTORS = '''\
|
BUILTIN_CONSTRUCTORS = '''\
|
||||||
import builtins
|
from six.moves import builtins
|
||||||
|
|
||||||
c1 = complex()
|
c1 = complex()
|
||||||
d1 = dict()
|
d1 = dict()
|
||||||
|
|
@ -38,6 +36,11 @@ t1 = ()
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def visitor():
|
||||||
|
return Visitor()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
('expression', 'calls'),
|
('expression', 'calls'),
|
||||||
[
|
[
|
||||||
|
|
@ -80,8 +83,7 @@ t1 = ()
|
||||||
('builtins.tuple()', []),
|
('builtins.tuple()', []),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_non_dict_exprs(expression, calls):
|
def test_non_dict_exprs(visitor, expression, calls):
|
||||||
visitor = Visitor(ignore=set())
|
|
||||||
visitor.visit(ast.parse(expression))
|
visitor.visit(ast.parse(expression))
|
||||||
assert visitor.builtin_type_calls == calls
|
assert visitor.builtin_type_calls == calls
|
||||||
|
|
||||||
|
|
@ -98,8 +100,7 @@ def test_non_dict_exprs(expression, calls):
|
||||||
('builtins.dict()', []),
|
('builtins.dict()', []),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_dict_allow_kwargs_exprs(expression, calls):
|
def test_dict_allow_kwargs_exprs(visitor, expression, calls):
|
||||||
visitor = Visitor(ignore=set())
|
|
||||||
visitor.visit(ast.parse(expression))
|
visitor.visit(ast.parse(expression))
|
||||||
assert visitor.builtin_type_calls == calls
|
assert visitor.builtin_type_calls == calls
|
||||||
|
|
||||||
|
|
@ -111,18 +112,17 @@ def test_dict_allow_kwargs_exprs(expression, calls):
|
||||||
('dict(a=1, b=2, c=3)', [Call('dict', 1, 0)]),
|
('dict(a=1, b=2, c=3)', [Call('dict', 1, 0)]),
|
||||||
("dict(**{'a': 1, 'b': 2, 'c': 3})", [Call('dict', 1, 0)]),
|
("dict(**{'a': 1, 'b': 2, 'c': 3})", [Call('dict', 1, 0)]),
|
||||||
('builtins.dict()', []),
|
('builtins.dict()', []),
|
||||||
pytest.param('f(dict())', [Call('dict', 1, 2)], id='nested'),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_dict_no_allow_kwargs_exprs(expression, calls):
|
def test_dict_no_allow_kwargs_exprs(expression, calls):
|
||||||
visitor = Visitor(ignore=set(), allow_dict_kwargs=False)
|
visitor = Visitor(allow_dict_kwargs=False)
|
||||||
visitor.visit(ast.parse(expression))
|
visitor.visit(ast.parse(expression))
|
||||||
assert visitor.builtin_type_calls == calls
|
assert visitor.builtin_type_calls == calls
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_constructors():
|
def test_ignore_constructors():
|
||||||
visitor = Visitor(
|
visitor = Visitor(
|
||||||
ignore={'complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'},
|
ignore=('complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'),
|
||||||
)
|
)
|
||||||
visitor.visit(ast.parse(BUILTIN_CONSTRUCTORS))
|
visitor.visit(ast.parse(BUILTIN_CONSTRUCTORS))
|
||||||
assert visitor.builtin_type_calls == []
|
assert visitor.builtin_type_calls == []
|
||||||
|
|
@ -131,19 +131,19 @@ def test_ignore_constructors():
|
||||||
def test_failing_file(tmpdir):
|
def test_failing_file(tmpdir):
|
||||||
f = tmpdir.join('f.py')
|
f = tmpdir.join('f.py')
|
||||||
f.write(BUILTIN_CONSTRUCTORS)
|
f.write(BUILTIN_CONSTRUCTORS)
|
||||||
rc = main([str(f)])
|
rc = main([f.strpath])
|
||||||
assert rc == 1
|
assert rc == 1
|
||||||
|
|
||||||
|
|
||||||
def test_passing_file(tmpdir):
|
def test_passing_file(tmpdir):
|
||||||
f = tmpdir.join('f.py')
|
f = tmpdir.join('f.py')
|
||||||
f.write(BUILTIN_LITERALS)
|
f.write(BUILTIN_LITERALS)
|
||||||
rc = main([str(f)])
|
rc = main([f.strpath])
|
||||||
assert rc == 0
|
assert rc == 0
|
||||||
|
|
||||||
|
|
||||||
def test_failing_file_ignore_all(tmpdir):
|
def test_failing_file_ignore_all(tmpdir):
|
||||||
f = tmpdir.join('f.py')
|
f = tmpdir.join('f.py')
|
||||||
f.write(BUILTIN_CONSTRUCTORS)
|
f.write(BUILTIN_CONSTRUCTORS)
|
||||||
rc = main(['--ignore=complex,dict,float,int,list,str,tuple', str(f)])
|
rc = main(['--ignore=complex,dict,float,int,list,str,tuple', f.strpath])
|
||||||
assert rc == 0
|
assert rc == 0
|
||||||
|
|
|
||||||
16
tests/check_byte_order_marker_test.py
Normal file
16
tests/check_byte_order_marker_test.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from pre_commit_hooks import check_byte_order_marker
|
||||||
|
|
||||||
|
|
||||||
|
def test_failure(tmpdir):
|
||||||
|
f = tmpdir.join('f.txt')
|
||||||
|
f.write_text('ohai', encoding='utf-8-sig')
|
||||||
|
assert check_byte_order_marker.main((f.strpath,)) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_success(tmpdir):
|
||||||
|
f = tmpdir.join('f.txt')
|
||||||
|
f.write_text('ohai', encoding='utf-8')
|
||||||
|
assert check_byte_order_marker.main((f.strpath,)) == 0
|
||||||
|
|
@ -1,26 +1,9 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from pre_commit_hooks.check_case_conflict import find_conflicting_filenames
|
from pre_commit_hooks.check_case_conflict import find_conflicting_filenames
|
||||||
from pre_commit_hooks.check_case_conflict import main
|
from pre_commit_hooks.check_case_conflict import main
|
||||||
from pre_commit_hooks.check_case_conflict import parents
|
|
||||||
from pre_commit_hooks.util import cmd_output
|
from pre_commit_hooks.util import cmd_output
|
||||||
from testing.util import git_commit
|
|
||||||
|
|
||||||
skip_win32 = pytest.mark.skipif(
|
|
||||||
sys.platform == 'win32',
|
|
||||||
reason='case conflicts between directories and files',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parents():
|
|
||||||
assert set(parents('a')) == set()
|
|
||||||
assert set(parents('a/b')) == {'a'}
|
|
||||||
assert set(parents('a/b/c')) == {'a/b', 'a'}
|
|
||||||
assert set(parents('a/b/c/d')) == {'a/b/c', 'a/b', 'a'}
|
|
||||||
|
|
||||||
|
|
||||||
def test_nothing_added(temp_git_dir):
|
def test_nothing_added(temp_git_dir):
|
||||||
|
|
@ -46,36 +29,6 @@ def test_adding_something_with_conflict(temp_git_dir):
|
||||||
assert find_conflicting_filenames(['f.py', 'F.py']) == 1
|
assert find_conflicting_filenames(['f.py', 'F.py']) == 1
|
||||||
|
|
||||||
|
|
||||||
@skip_win32 # pragma: win32 no cover
|
|
||||||
def test_adding_files_with_conflicting_directories(temp_git_dir):
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
temp_git_dir.mkdir('dir').join('x').write('foo')
|
|
||||||
temp_git_dir.mkdir('DIR').join('y').write('foo')
|
|
||||||
cmd_output('git', 'add', '-A')
|
|
||||||
|
|
||||||
assert find_conflicting_filenames([]) == 1
|
|
||||||
|
|
||||||
|
|
||||||
@skip_win32 # pragma: win32 no cover
|
|
||||||
def test_adding_files_with_conflicting_deep_directories(temp_git_dir):
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
temp_git_dir.mkdir('x').mkdir('y').join('z').write('foo')
|
|
||||||
temp_git_dir.join('X').write('foo')
|
|
||||||
cmd_output('git', 'add', '-A')
|
|
||||||
|
|
||||||
assert find_conflicting_filenames([]) == 1
|
|
||||||
|
|
||||||
|
|
||||||
@skip_win32 # pragma: win32 no cover
|
|
||||||
def test_adding_file_with_conflicting_directory(temp_git_dir):
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
temp_git_dir.mkdir('dir').join('x').write('foo')
|
|
||||||
temp_git_dir.join('DIR').write('foo')
|
|
||||||
cmd_output('git', 'add', '-A')
|
|
||||||
|
|
||||||
assert find_conflicting_filenames([]) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_added_file_not_in_pre_commits_list(temp_git_dir):
|
def test_added_file_not_in_pre_commits_list(temp_git_dir):
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
temp_git_dir.join('f.py').write("print('hello world')")
|
temp_git_dir.join('f.py').write("print('hello world')")
|
||||||
|
|
@ -88,7 +41,7 @@ def test_file_conflicts_with_committed_file(temp_git_dir):
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
temp_git_dir.join('f.py').write("print('hello world')")
|
temp_git_dir.join('f.py').write("print('hello world')")
|
||||||
cmd_output('git', 'add', 'f.py')
|
cmd_output('git', 'add', 'f.py')
|
||||||
git_commit('-m', 'Add f.py')
|
cmd_output('git', 'commit', '--no-gpg-sign', '-n', '-m', 'Add f.py')
|
||||||
|
|
||||||
temp_git_dir.join('F.py').write("print('hello world')")
|
temp_git_dir.join('F.py').write("print('hello world')")
|
||||||
cmd_output('git', 'add', 'F.py')
|
cmd_output('git', 'add', 'F.py')
|
||||||
|
|
@ -96,19 +49,6 @@ def test_file_conflicts_with_committed_file(temp_git_dir):
|
||||||
assert find_conflicting_filenames(['F.py']) == 1
|
assert find_conflicting_filenames(['F.py']) == 1
|
||||||
|
|
||||||
|
|
||||||
@skip_win32 # pragma: win32 no cover
|
|
||||||
def test_file_conflicts_with_committed_dir(temp_git_dir):
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
temp_git_dir.mkdir('dir').join('x').write('foo')
|
|
||||||
cmd_output('git', 'add', '-A')
|
|
||||||
git_commit('-m', 'Add f.py')
|
|
||||||
|
|
||||||
temp_git_dir.join('DIR').write('foo')
|
|
||||||
cmd_output('git', 'add', '-A')
|
|
||||||
|
|
||||||
assert find_conflicting_filenames([]) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_integration(temp_git_dir):
|
def test_integration(temp_git_dir):
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
assert main(argv=[]) == 0
|
assert main(argv=[]) == 0
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
from __future__ import annotations
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -17,7 +19,7 @@ TESTS = (
|
||||||
b'from __future__ import unicode_literals\n'
|
b'from __future__ import unicode_literals\n'
|
||||||
b'"foo"\n',
|
b'"foo"\n',
|
||||||
1,
|
1,
|
||||||
'{filename}:2: Module docstring appears after code '
|
'{filename}:2 Module docstring appears after code '
|
||||||
'(code seen on line 1).\n',
|
'(code seen on line 1).\n',
|
||||||
),
|
),
|
||||||
# Test double docstring
|
# Test double docstring
|
||||||
|
|
@ -26,7 +28,7 @@ TESTS = (
|
||||||
b'from __future__ import absolute_import\n'
|
b'from __future__ import absolute_import\n'
|
||||||
b'"fake docstring"\n',
|
b'"fake docstring"\n',
|
||||||
1,
|
1,
|
||||||
'{filename}:3: Multiple module docstrings '
|
'{filename}:3 Multiple module docstrings '
|
||||||
'(first docstring on line 1).\n',
|
'(first docstring on line 1).\n',
|
||||||
),
|
),
|
||||||
# Test multiple lines of code above
|
# Test multiple lines of code above
|
||||||
|
|
@ -35,7 +37,7 @@ TESTS = (
|
||||||
b'import sys\n'
|
b'import sys\n'
|
||||||
b'"docstring"\n',
|
b'"docstring"\n',
|
||||||
1,
|
1,
|
||||||
'{filename}:3: Module docstring appears after code '
|
'{filename}:3 Module docstring appears after code '
|
||||||
'(code seen on line 1).\n',
|
'(code seen on line 1).\n',
|
||||||
),
|
),
|
||||||
# String literals in expressions are ok.
|
# String literals in expressions are ok.
|
||||||
|
|
@ -58,12 +60,12 @@ def test_unit(capsys, contents, expected, expected_out):
|
||||||
def test_integration(tmpdir, capsys, contents, expected, expected_out):
|
def test_integration(tmpdir, capsys, contents, expected, expected_out):
|
||||||
f = tmpdir.join('test.py')
|
f = tmpdir.join('test.py')
|
||||||
f.write_binary(contents)
|
f.write_binary(contents)
|
||||||
assert main([str(f)]) == expected
|
assert main([f.strpath]) == expected
|
||||||
assert capsys.readouterr()[0] == expected_out.format(filename=str(f))
|
assert capsys.readouterr()[0] == expected_out.format(filename=f.strpath)
|
||||||
|
|
||||||
|
|
||||||
def test_arbitrary_encoding(tmpdir):
|
def test_arbitrary_encoding(tmpdir):
|
||||||
f = tmpdir.join('f.py')
|
f = tmpdir.join('f.py')
|
||||||
contents = '# -*- coding: cp1252\nx = "£"'.encode('cp1252')
|
contents = '# -*- coding: cp1252\nx = "£"'.encode('cp1252')
|
||||||
f.write_binary(contents)
|
f.write_binary(contents)
|
||||||
assert main([str(f)]) == 0
|
assert main([f.strpath]) == 0
|
||||||
|
|
|
||||||
|
|
@ -1,127 +1,39 @@
|
||||||
from __future__ import annotations
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import
|
||||||
import os
|
from __future__ import unicode_literals
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks import check_executables_have_shebangs
|
|
||||||
from pre_commit_hooks.check_executables_have_shebangs import main
|
from pre_commit_hooks.check_executables_have_shebangs import main
|
||||||
from pre_commit_hooks.util import cmd_output
|
|
||||||
|
|
||||||
skip_win32 = pytest.mark.skipif(
|
|
||||||
sys.platform == 'win32',
|
|
||||||
reason="non-git checks aren't relevant on windows",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@skip_win32 # pragma: win32 no cover
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'content', (
|
'content', (
|
||||||
b'#!/bin/bash\nhello world\n',
|
b'#!/bin/bash\nhello world\n',
|
||||||
b'#!/usr/bin/env python3.6',
|
b'#!/usr/bin/env python3.6',
|
||||||
b'#!python',
|
b'#!python',
|
||||||
'#!☃'.encode(),
|
'#!☃'.encode('UTF-8'),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_has_shebang(content, tmpdir):
|
def test_has_shebang(content, tmpdir):
|
||||||
path = tmpdir.join('path')
|
path = tmpdir.join('path')
|
||||||
path.write(content, 'wb')
|
path.write(content, 'wb')
|
||||||
assert main((str(path),)) == 0
|
assert main((path.strpath,)) == 0
|
||||||
|
|
||||||
|
|
||||||
@skip_win32 # pragma: win32 no cover
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'content', (
|
'content', (
|
||||||
b'',
|
b'',
|
||||||
b' #!python\n',
|
b' #!python\n',
|
||||||
b'\n#!python\n',
|
b'\n#!python\n',
|
||||||
b'python\n',
|
b'python\n',
|
||||||
'☃'.encode(),
|
'☃'.encode('UTF-8'),
|
||||||
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_bad_shebang(content, tmpdir, capsys):
|
def test_bad_shebang(content, tmpdir, capsys):
|
||||||
path = tmpdir.join('path')
|
path = tmpdir.join('path')
|
||||||
path.write(content, 'wb')
|
path.write(content, 'wb')
|
||||||
assert main((str(path),)) == 1
|
assert main((path.strpath,)) == 1
|
||||||
_, stderr = capsys.readouterr()
|
_, stderr = capsys.readouterr()
|
||||||
assert stderr.startswith(f'{path}: marked executable but')
|
assert stderr.startswith('{}: marked executable but'.format(path.strpath))
|
||||||
|
|
||||||
|
|
||||||
def test_check_git_filemode_passing(tmpdir):
|
|
||||||
with tmpdir.as_cwd():
|
|
||||||
cmd_output('git', 'init', '.')
|
|
||||||
|
|
||||||
f = tmpdir.join('f')
|
|
||||||
f.write('#!/usr/bin/env bash')
|
|
||||||
f_path = str(f)
|
|
||||||
cmd_output('chmod', '+x', f_path)
|
|
||||||
cmd_output('git', 'add', f_path)
|
|
||||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
|
||||||
|
|
||||||
g = tmpdir.join('g').ensure()
|
|
||||||
g_path = str(g)
|
|
||||||
cmd_output('git', 'add', g_path)
|
|
||||||
|
|
||||||
# this is potentially a problem, but not something the script intends
|
|
||||||
# to check for -- we're only making sure that things that are
|
|
||||||
# executable have shebangs
|
|
||||||
h = tmpdir.join('h')
|
|
||||||
h.write('#!/usr/bin/env bash')
|
|
||||||
h_path = str(h)
|
|
||||||
cmd_output('git', 'add', h_path)
|
|
||||||
|
|
||||||
files = (f_path, g_path, h_path)
|
|
||||||
assert check_executables_have_shebangs._check_git_filemode(files) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_git_filemode_passing_unusual_characters(tmpdir):
|
|
||||||
with tmpdir.as_cwd():
|
|
||||||
cmd_output('git', 'init', '.')
|
|
||||||
|
|
||||||
f = tmpdir.join('mañana.txt')
|
|
||||||
f.write('#!/usr/bin/env bash')
|
|
||||||
f_path = str(f)
|
|
||||||
cmd_output('chmod', '+x', f_path)
|
|
||||||
cmd_output('git', 'add', f_path)
|
|
||||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
|
||||||
|
|
||||||
files = (f_path,)
|
|
||||||
assert check_executables_have_shebangs._check_git_filemode(files) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_git_filemode_failing(tmpdir):
|
|
||||||
with tmpdir.as_cwd():
|
|
||||||
cmd_output('git', 'init', '.')
|
|
||||||
|
|
||||||
f = tmpdir.join('f').ensure()
|
|
||||||
f_path = str(f)
|
|
||||||
cmd_output('chmod', '+x', f_path)
|
|
||||||
cmd_output('git', 'add', f_path)
|
|
||||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
|
||||||
|
|
||||||
files = (f_path,)
|
|
||||||
assert check_executables_have_shebangs._check_git_filemode(files) == 1
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
('content', 'mode', 'expected'),
|
|
||||||
(
|
|
||||||
pytest.param('#!python', '+x', 0, id='shebang with executable'),
|
|
||||||
pytest.param('#!python', '-x', 0, id='shebang without executable'),
|
|
||||||
pytest.param('', '+x', 1, id='no shebang with executable'),
|
|
||||||
pytest.param('', '-x', 0, id='no shebang without executable'),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_git_executable_shebang(temp_git_dir, content, mode, expected):
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
path = temp_git_dir.join('path')
|
|
||||||
path.write(content)
|
|
||||||
cmd_output('git', 'add', str(path))
|
|
||||||
cmd_output('chmod', mode, str(path))
|
|
||||||
cmd_output('git', 'update-index', f'--chmod={mode}', str(path))
|
|
||||||
|
|
||||||
# simulate how identify chooses that something is executable
|
|
||||||
filenames = [path for path in [str(path)] if os.access(path, os.X_OK)]
|
|
||||||
|
|
||||||
assert main(filenames) == expected
|
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os.path
|
|
||||||
import re
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from pre_commit_hooks.check_yaml import yaml
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='module')
|
|
||||||
def hook_re():
|
|
||||||
here = os.path.dirname(__file__)
|
|
||||||
with open(os.path.join(here, '..', '.pre-commit-hooks.yaml')) as f:
|
|
||||||
hook_defs = yaml.load(f)
|
|
||||||
hook, = (
|
|
||||||
hook
|
|
||||||
for hook in hook_defs
|
|
||||||
if hook['id'] == 'check-illegal-windows-names'
|
|
||||||
)
|
|
||||||
yield re.compile(hook['files'])
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
's',
|
|
||||||
(
|
|
||||||
pytest.param('aux.txt', id='with ext'),
|
|
||||||
pytest.param('aux', id='without ext'),
|
|
||||||
pytest.param('AuX.tXt', id='capitals'),
|
|
||||||
pytest.param('com7.dat', id='com with digit'),
|
|
||||||
pytest.param(':', id='bare colon'),
|
|
||||||
pytest.param('file:Zone.Identifier', id='mid colon'),
|
|
||||||
pytest.param('path/COM¹.json', id='com with superscript'),
|
|
||||||
pytest.param('dir/LPT³.toml', id='lpt with superscript'),
|
|
||||||
pytest.param('with < less than', id='with less than'),
|
|
||||||
pytest.param('Fast or Slow?.md', id='with question mark'),
|
|
||||||
pytest.param('with "double" quotes', id='with double quotes'),
|
|
||||||
pytest.param('with_null\x00byte', id='with null byte'),
|
|
||||||
pytest.param('ends_with.', id='ends with period'),
|
|
||||||
pytest.param('ends_with ', id='ends with space'),
|
|
||||||
pytest.param('ends_with\t', id='ends with tab'),
|
|
||||||
pytest.param('dir/ends./with.txt', id='directory ends with period'),
|
|
||||||
pytest.param('dir/ends /with.txt', id='directory ends with space'),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_check_illegal_windows_names_matches(hook_re, s):
|
|
||||||
assert hook_re.search(s)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
's',
|
|
||||||
(
|
|
||||||
pytest.param('README.md', id='standard file'),
|
|
||||||
pytest.param('foo.aux', id='as ext'),
|
|
||||||
pytest.param('com.dat', id='com without digit'),
|
|
||||||
pytest.param('.python-version', id='starts with period'),
|
|
||||||
pytest.param(' pseudo nan', id='with spaces'),
|
|
||||||
pytest.param('!@#$%^&;=≤\'~`¡¿€🤗', id='with allowed characters'),
|
|
||||||
pytest.param('path.to/file.py', id='standard path'),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_check_illegal_windows_names_does_not_match(hook_re, s):
|
|
||||||
assert hook_re.search(s) is None
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks.check_json import main
|
from pre_commit_hooks.check_json import main
|
||||||
|
|
@ -11,7 +9,6 @@ from testing.util import get_resource_path
|
||||||
('bad_json.notjson', 1),
|
('bad_json.notjson', 1),
|
||||||
('bad_json_latin1.nonjson', 1),
|
('bad_json_latin1.nonjson', 1),
|
||||||
('ok_json.json', 0),
|
('ok_json.json', 0),
|
||||||
('duplicate_key_json.notjson', 1),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_main(capsys, filename, expected_retval):
|
def test_main(capsys, filename, expected_retval):
|
||||||
|
|
@ -20,9 +17,3 @@ def test_main(capsys, filename, expected_retval):
|
||||||
if expected_retval == 1:
|
if expected_retval == 1:
|
||||||
stdout, _ = capsys.readouterr()
|
stdout, _ = capsys.readouterr()
|
||||||
assert filename in stdout
|
assert filename in stdout
|
||||||
|
|
||||||
|
|
||||||
def test_non_utf8_file(tmpdir):
|
|
||||||
f = tmpdir.join('t.json')
|
|
||||||
f.write_binary(b'\xa9\xfe\x12')
|
|
||||||
assert main((str(f),))
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
@ -8,7 +9,6 @@ import pytest
|
||||||
from pre_commit_hooks.check_merge_conflict import main
|
from pre_commit_hooks.check_merge_conflict import main
|
||||||
from pre_commit_hooks.util import cmd_output
|
from pre_commit_hooks.util import cmd_output
|
||||||
from testing.util import get_resource_path
|
from testing.util import get_resource_path
|
||||||
from testing.util import git_commit
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -19,23 +19,23 @@ def f1_is_a_conflict_file(tmpdir):
|
||||||
repo2 = tmpdir.join('repo2')
|
repo2 = tmpdir.join('repo2')
|
||||||
repo2_f1 = repo2.join('f1')
|
repo2_f1 = repo2.join('f1')
|
||||||
|
|
||||||
cmd_output('git', 'init', '--', str(repo1))
|
cmd_output('git', 'init', '--', repo1.strpath)
|
||||||
with repo1.as_cwd():
|
with repo1.as_cwd():
|
||||||
repo1_f1.ensure()
|
repo1_f1.ensure()
|
||||||
cmd_output('git', 'add', '.')
|
cmd_output('git', 'add', '.')
|
||||||
git_commit('-m', 'commit1')
|
cmd_output('git', 'commit', '--no-gpg-sign', '-m', 'commit1')
|
||||||
|
|
||||||
cmd_output('git', 'clone', str(repo1), str(repo2))
|
cmd_output('git', 'clone', repo1.strpath, repo2.strpath)
|
||||||
|
|
||||||
# Commit in mainline
|
# Commit in master
|
||||||
with repo1.as_cwd():
|
with repo1.as_cwd():
|
||||||
repo1_f1.write('parent\n')
|
repo1_f1.write('parent\n')
|
||||||
git_commit('-am', 'mainline commit2')
|
cmd_output('git', 'commit', '--no-gpg-sign', '-am', 'master commit2')
|
||||||
|
|
||||||
# Commit in clone and pull
|
# Commit in clone and pull
|
||||||
with repo2.as_cwd():
|
with repo2.as_cwd():
|
||||||
repo2_f1.write('child\n')
|
repo2_f1.write('child\n')
|
||||||
git_commit('-am', 'clone commit2')
|
cmd_output('git', 'commit', '--no-gpg-sign', '-am', 'clone commit2')
|
||||||
cmd_output('git', 'pull', '--no-rebase', retcode=None)
|
cmd_output('git', 'pull', '--no-rebase', retcode=None)
|
||||||
# We should end up in a merge conflict!
|
# We should end up in a merge conflict!
|
||||||
f1 = repo2_f1.read()
|
f1 = repo2_f1.read()
|
||||||
|
|
@ -74,24 +74,24 @@ def repository_pending_merge(tmpdir):
|
||||||
repo2 = tmpdir.join('repo2')
|
repo2 = tmpdir.join('repo2')
|
||||||
repo2_f1 = repo2.join('f1')
|
repo2_f1 = repo2.join('f1')
|
||||||
repo2_f2 = repo2.join('f2')
|
repo2_f2 = repo2.join('f2')
|
||||||
cmd_output('git', 'init', str(repo1))
|
cmd_output('git', 'init', repo1.strpath)
|
||||||
with repo1.as_cwd():
|
with repo1.as_cwd():
|
||||||
repo1_f1.ensure()
|
repo1_f1.ensure()
|
||||||
cmd_output('git', 'add', '.')
|
cmd_output('git', 'add', '.')
|
||||||
git_commit('-m', 'commit1')
|
cmd_output('git', 'commit', '--no-gpg-sign', '-m', 'commit1')
|
||||||
|
|
||||||
cmd_output('git', 'clone', str(repo1), str(repo2))
|
cmd_output('git', 'clone', repo1.strpath, repo2.strpath)
|
||||||
|
|
||||||
# Commit in mainline
|
# Commit in master
|
||||||
with repo1.as_cwd():
|
with repo1.as_cwd():
|
||||||
repo1_f1.write('parent\n')
|
repo1_f1.write('parent\n')
|
||||||
git_commit('-am', 'mainline commit2')
|
cmd_output('git', 'commit', '--no-gpg-sign', '-am', 'master commit2')
|
||||||
|
|
||||||
# Commit in clone and pull without committing
|
# Commit in clone and pull without committing
|
||||||
with repo2.as_cwd():
|
with repo2.as_cwd():
|
||||||
repo2_f2.write('child\n')
|
repo2_f2.write('child\n')
|
||||||
cmd_output('git', 'add', '.')
|
cmd_output('git', 'add', '.')
|
||||||
git_commit('-m', 'clone commit2')
|
cmd_output('git', 'commit', '--no-gpg-sign', '-m', 'clone commit2')
|
||||||
cmd_output('git', 'pull', '--no-commit', '--no-rebase')
|
cmd_output('git', 'pull', '--no-commit', '--no-rebase')
|
||||||
# We should end up in a pending merge
|
# We should end up in a pending merge
|
||||||
assert repo2_f1.read() == 'parent\n'
|
assert repo2_f1.read() == 'parent\n'
|
||||||
|
|
@ -101,18 +101,12 @@ def repository_pending_merge(tmpdir):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('f1_is_a_conflict_file')
|
@pytest.mark.usefixtures('f1_is_a_conflict_file')
|
||||||
def test_merge_conflicts_git(capsys):
|
def test_merge_conflicts_git():
|
||||||
assert main(['f1']) == 1
|
assert main(['f1']) == 1
|
||||||
out, _ = capsys.readouterr()
|
|
||||||
assert out == (
|
|
||||||
"f1:1: Merge conflict string '<<<<<<<' found\n"
|
|
||||||
"f1:3: Merge conflict string '=======' found\n"
|
|
||||||
"f1:5: Merge conflict string '>>>>>>>' found\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'contents', (b'<<<<<<< HEAD\n', b'=======\n', b'>>>>>>> main\n'),
|
'contents', (b'<<<<<<< HEAD\n', b'=======\n', b'>>>>>>> master\n'),
|
||||||
)
|
)
|
||||||
def test_merge_conflicts_failing(contents, repository_pending_merge):
|
def test_merge_conflicts_failing(contents, repository_pending_merge):
|
||||||
repository_pending_merge.join('f2').write_binary(contents)
|
repository_pending_merge.join('f2').write_binary(contents)
|
||||||
|
|
@ -143,15 +137,3 @@ def test_care_when_assumed_merge(tmpdir):
|
||||||
f = tmpdir.join('README.md')
|
f = tmpdir.join('README.md')
|
||||||
f.write_binary(b'problem\n=======\n')
|
f.write_binary(b'problem\n=======\n')
|
||||||
assert main([str(f.realpath()), '--assume-in-merge']) == 1
|
assert main([str(f.realpath()), '--assume-in-merge']) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_worktree_merge_conflicts(f1_is_a_conflict_file, tmpdir, capsys):
|
|
||||||
worktree = tmpdir.join('worktree')
|
|
||||||
cmd_output('git', 'worktree', 'add', str(worktree))
|
|
||||||
with worktree.as_cwd():
|
|
||||||
cmd_output(
|
|
||||||
'git', 'pull', '--no-rebase', 'origin', 'HEAD', retcode=None,
|
|
||||||
)
|
|
||||||
msg = f1_is_a_conflict_file.join('.git/worktrees/worktree/MERGE_MSG')
|
|
||||||
assert msg.exists()
|
|
||||||
test_merge_conflicts_git(capsys)
|
|
||||||
|
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from pre_commit_hooks.check_shebang_scripts_are_executable import \
|
|
||||||
_check_git_filemode
|
|
||||||
from pre_commit_hooks.check_shebang_scripts_are_executable import main
|
|
||||||
from pre_commit_hooks.util import cmd_output
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_git_filemode_passing(tmpdir):
|
|
||||||
with tmpdir.as_cwd():
|
|
||||||
cmd_output('git', 'init', '.')
|
|
||||||
|
|
||||||
f = tmpdir.join('f')
|
|
||||||
f.write('#!/usr/bin/env bash')
|
|
||||||
f_path = str(f)
|
|
||||||
cmd_output('chmod', '+x', f_path)
|
|
||||||
cmd_output('git', 'add', f_path)
|
|
||||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
|
||||||
|
|
||||||
g = tmpdir.join('g').ensure()
|
|
||||||
g_path = str(g)
|
|
||||||
cmd_output('git', 'add', g_path)
|
|
||||||
|
|
||||||
files = [f_path, g_path]
|
|
||||||
assert _check_git_filemode(files) == 0
|
|
||||||
|
|
||||||
# this is the one we should trigger on
|
|
||||||
h = tmpdir.join('h')
|
|
||||||
h.write('#!/usr/bin/env bash')
|
|
||||||
h_path = str(h)
|
|
||||||
cmd_output('git', 'add', h_path)
|
|
||||||
|
|
||||||
files = [h_path]
|
|
||||||
assert _check_git_filemode(files) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_git_filemode_passing_unusual_characters(tmpdir):
|
|
||||||
with tmpdir.as_cwd():
|
|
||||||
cmd_output('git', 'init', '.')
|
|
||||||
|
|
||||||
f = tmpdir.join('mañana.txt')
|
|
||||||
f.write('#!/usr/bin/env bash')
|
|
||||||
f_path = str(f)
|
|
||||||
cmd_output('chmod', '+x', f_path)
|
|
||||||
cmd_output('git', 'add', f_path)
|
|
||||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
|
||||||
|
|
||||||
files = (f_path,)
|
|
||||||
assert _check_git_filemode(files) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_git_filemode_failing(tmpdir):
|
|
||||||
with tmpdir.as_cwd():
|
|
||||||
cmd_output('git', 'init', '.')
|
|
||||||
|
|
||||||
f = tmpdir.join('f').ensure()
|
|
||||||
f.write('#!/usr/bin/env bash')
|
|
||||||
f_path = str(f)
|
|
||||||
cmd_output('git', 'add', f_path)
|
|
||||||
|
|
||||||
files = (f_path,)
|
|
||||||
assert _check_git_filemode(files) == 1
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
('content', 'mode', 'expected'),
|
|
||||||
(
|
|
||||||
pytest.param('#!python', '+x', 0, id='shebang with executable'),
|
|
||||||
pytest.param('#!python', '-x', 1, id='shebang without executable'),
|
|
||||||
pytest.param('', '+x', 0, id='no shebang with executable'),
|
|
||||||
pytest.param('', '-x', 0, id='no shebang without executable'),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_git_executable_shebang(temp_git_dir, content, mode, expected):
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
path = temp_git_dir.join('path')
|
|
||||||
path.write(content)
|
|
||||||
cmd_output('git', 'add', str(path))
|
|
||||||
cmd_output('chmod', mode, str(path))
|
|
||||||
cmd_output('git', 'update-index', f'--chmod={mode}', str(path))
|
|
||||||
|
|
||||||
# simulate how identify chooses that something is executable
|
|
||||||
filenames = [path for path in [str(path)] if os.access(path, os.X_OK)]
|
|
||||||
|
|
||||||
assert main(filenames) == expected
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -18,8 +16,8 @@ def test_main(tmpdir, dest, expected): # pragma: no cover (symlinks)
|
||||||
tmpdir.join('exists').ensure()
|
tmpdir.join('exists').ensure()
|
||||||
symlink = tmpdir.join('symlink')
|
symlink = tmpdir.join('symlink')
|
||||||
symlink.mksymlinkto(tmpdir.join(dest))
|
symlink.mksymlinkto(tmpdir.join(dest))
|
||||||
assert main((str(symlink),)) == expected
|
assert main((symlink.strpath,)) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_main_normal_file(tmpdir):
|
def test_main_normal_file(tmpdir):
|
||||||
assert main((str(tmpdir.join('f').ensure()),)) == 0
|
assert main((tmpdir.join('f').ensure().strpath,)) == 0
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from pre_commit_hooks.check_toml import main
|
from pre_commit_hooks.check_toml import main
|
||||||
|
|
||||||
|
|
||||||
def test_toml_bad(tmpdir):
|
def test_toml_good(tmpdir):
|
||||||
filename = tmpdir.join('f')
|
filename = tmpdir.join('f')
|
||||||
filename.write("""
|
filename.write("""
|
||||||
key = # INVALID
|
key = # INVALID
|
||||||
|
|
||||||
= "no key name" # INVALID
|
= "no key name" # INVALID
|
||||||
""")
|
""")
|
||||||
ret = main((str(filename),))
|
ret = main((filename.strpath,))
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
|
|
||||||
|
|
||||||
def test_toml_good(tmpdir):
|
def test_toml_bad(tmpdir):
|
||||||
filename = tmpdir.join('f')
|
filename = tmpdir.join('f')
|
||||||
filename.write(
|
filename.write(
|
||||||
"""
|
"""
|
||||||
|
|
@ -27,12 +28,5 @@ name = "John"
|
||||||
dob = 1979-05-27T07:32:00-08:00 # First class dates
|
dob = 1979-05-27T07:32:00-08:00 # First class dates
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
ret = main((str(filename),))
|
ret = main((filename.strpath,))
|
||||||
assert ret == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_toml_good_unicode(tmpdir):
|
|
||||||
filename = tmpdir.join('f')
|
|
||||||
filename.write_binary('letter = "\N{SNOWMAN}"\n'.encode())
|
|
||||||
ret = main((str(filename),))
|
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from pre_commit_hooks.check_vcs_permalinks import main
|
from pre_commit_hooks.check_vcs_permalinks import main
|
||||||
|
|
||||||
|
|
||||||
def test_trivial(tmpdir):
|
def test_trivial(tmpdir):
|
||||||
f = tmpdir.join('f.txt').ensure()
|
f = tmpdir.join('f.txt').ensure()
|
||||||
assert not main((str(f),))
|
assert not main((f.strpath,))
|
||||||
|
|
||||||
|
|
||||||
def test_passing(tmpdir):
|
def test_passing(tmpdir):
|
||||||
|
|
@ -13,28 +14,24 @@ def test_passing(tmpdir):
|
||||||
f.write_binary(
|
f.write_binary(
|
||||||
# permalinks are ok
|
# permalinks are ok
|
||||||
b'https://github.com/asottile/test/blob/649e6/foo%20bar#L1\n'
|
b'https://github.com/asottile/test/blob/649e6/foo%20bar#L1\n'
|
||||||
# tags are ok
|
|
||||||
b'https://github.com/asottile/test/blob/1.0.0/foo%20bar#L1\n'
|
|
||||||
# links to files but not line numbers are ok
|
# links to files but not line numbers are ok
|
||||||
b'https://github.com/asottile/test/blob/main/foo%20bar\n'
|
b'https://github.com/asottile/test/blob/master/foo%20bar\n'
|
||||||
# regression test for overly-greedy regex
|
# regression test for overly-greedy regex
|
||||||
b'https://github.com/ yes / no ? /blob/main/foo#L1\n',
|
b'https://github.com/ yes / no ? /blob/master/foo#L1\n',
|
||||||
)
|
)
|
||||||
assert not main((str(f),))
|
assert not main((f.strpath,))
|
||||||
|
|
||||||
|
|
||||||
def test_failing(tmpdir, capsys):
|
def test_failing(tmpdir, capsys):
|
||||||
with tmpdir.as_cwd():
|
with tmpdir.as_cwd():
|
||||||
tmpdir.join('f.txt').write_binary(
|
tmpdir.join('f.txt').write_binary(
|
||||||
b'https://github.com/asottile/test/blob/main/foo#L1\n'
|
b'https://github.com/asottile/test/blob/master/foo#L1\n',
|
||||||
b'https://example.com/asottile/test/blob/main/foo#L1\n',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert main(('f.txt', '--additional-github-domain', 'example.com'))
|
assert main(('f.txt',))
|
||||||
out, _ = capsys.readouterr()
|
out, _ = capsys.readouterr()
|
||||||
assert out == (
|
assert out == (
|
||||||
'f.txt:1:https://github.com/asottile/test/blob/main/foo#L1\n'
|
'f.txt:1:https://github.com/asottile/test/blob/master/foo#L1\n'
|
||||||
'f.txt:2:https://example.com/asottile/test/blob/main/foo#L1\n'
|
|
||||||
'\n'
|
'\n'
|
||||||
'Non-permanent github link detected.\n'
|
'Non-permanent github link detected.\n'
|
||||||
'On any page on github press [y] to load a permalink.\n'
|
'On any page on github press [y] to load a permalink.\n'
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks.check_xml import main
|
from pre_commit_hooks.check_xml import main
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -22,16 +23,16 @@ def test_main_allow_multiple_documents(tmpdir):
|
||||||
f.write('---\nfoo\n---\nbar\n')
|
f.write('---\nfoo\n---\nbar\n')
|
||||||
|
|
||||||
# should fail without the setting
|
# should fail without the setting
|
||||||
assert main((str(f),))
|
assert main((f.strpath,))
|
||||||
|
|
||||||
# should pass when we allow multiple documents
|
# should pass when we allow multiple documents
|
||||||
assert not main(('--allow-multiple-documents', str(f)))
|
assert not main(('--allow-multiple-documents', f.strpath))
|
||||||
|
|
||||||
|
|
||||||
def test_fails_even_with_allow_multiple_documents(tmpdir):
|
def test_fails_even_with_allow_multiple_documents(tmpdir):
|
||||||
f = tmpdir.join('test.yaml')
|
f = tmpdir.join('test.yaml')
|
||||||
f.write('[')
|
f.write('[')
|
||||||
assert main(('--allow-multiple-documents', str(f)))
|
assert main(('--allow-multiple-documents', f.strpath))
|
||||||
|
|
||||||
|
|
||||||
def test_main_unsafe(tmpdir):
|
def test_main_unsafe(tmpdir):
|
||||||
|
|
@ -42,12 +43,12 @@ def test_main_unsafe(tmpdir):
|
||||||
' deadbeefdeadbeefdeadbeef\n',
|
' deadbeefdeadbeefdeadbeef\n',
|
||||||
)
|
)
|
||||||
# should fail "safe" check
|
# should fail "safe" check
|
||||||
assert main((str(f),))
|
assert main((f.strpath,))
|
||||||
# should pass when we allow unsafe documents
|
# should pass when we allow unsafe documents
|
||||||
assert not main(('--unsafe', str(f)))
|
assert not main(('--unsafe', f.strpath))
|
||||||
|
|
||||||
|
|
||||||
def test_main_unsafe_still_fails_on_syntax_errors(tmpdir):
|
def test_main_unsafe_still_fails_on_syntax_errors(tmpdir):
|
||||||
f = tmpdir.join('test.yaml')
|
f = tmpdir.join('test.yaml')
|
||||||
f.write('[')
|
f.write('[')
|
||||||
assert main(('--unsafe', str(f)))
|
assert main(('--unsafe', f.strpath))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -8,5 +10,5 @@ from pre_commit_hooks.util import cmd_output
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temp_git_dir(tmpdir):
|
def temp_git_dir(tmpdir):
|
||||||
git_dir = tmpdir.join('gits')
|
git_dir = tmpdir.join('gits')
|
||||||
cmd_output('git', 'init', '--', str(git_dir))
|
cmd_output('git', 'init', '--', git_dir.strpath)
|
||||||
yield git_dir
|
yield git_dir
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
from __future__ import annotations
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
|
|
@ -35,7 +37,7 @@ def test_finds_breakpoint():
|
||||||
def test_returns_one_for_failing_file(tmpdir):
|
def test_returns_one_for_failing_file(tmpdir):
|
||||||
f_py = tmpdir.join('f.py')
|
f_py = tmpdir.join('f.py')
|
||||||
f_py.write('def f():\n import pdb; pdb.set_trace()')
|
f_py.write('def f():\n import pdb; pdb.set_trace()')
|
||||||
ret = main([str(f_py)])
|
ret = main([f_py.strpath])
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,12 +54,10 @@ def test_syntaxerror_file():
|
||||||
def test_non_utf8_file(tmpdir):
|
def test_non_utf8_file(tmpdir):
|
||||||
f_py = tmpdir.join('f.py')
|
f_py = tmpdir.join('f.py')
|
||||||
f_py.write_binary('# -*- coding: cp1252 -*-\nx = "€"\n'.encode('cp1252'))
|
f_py.write_binary('# -*- coding: cp1252 -*-\nx = "€"\n'.encode('cp1252'))
|
||||||
assert main((str(f_py),)) == 0
|
assert main((f_py.strpath,)) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_py37_breakpoint(tmpdir, capsys):
|
def test_py37_breakpoint(tmpdir):
|
||||||
f_py = tmpdir.join('f.py')
|
f_py = tmpdir.join('f.py')
|
||||||
f_py.write('def f():\n breakpoint()\n')
|
f_py.write('def f():\n breakpoint()\n')
|
||||||
assert main((str(f_py),)) == 1
|
assert main((f_py.strpath,)) == 1
|
||||||
out, _ = capsys.readouterr()
|
|
||||||
assert out == f'{f_py}:2:4: breakpoint called\n'
|
|
||||||
|
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from pre_commit_hooks.destroyed_symlinks import find_destroyed_symlinks
|
|
||||||
from pre_commit_hooks.destroyed_symlinks import main
|
|
||||||
from testing.util import git_commit
|
|
||||||
|
|
||||||
TEST_SYMLINK = 'test_symlink'
|
|
||||||
TEST_SYMLINK_TARGET = '/doesnt/really/matters'
|
|
||||||
TEST_FILE = 'test_file'
|
|
||||||
TEST_FILE_RENAMED = f'{TEST_FILE}_renamed'
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def repo_with_destroyed_symlink(tmpdir):
|
|
||||||
source_repo = tmpdir.join('src')
|
|
||||||
os.makedirs(source_repo, exist_ok=True)
|
|
||||||
test_repo = tmpdir.join('test')
|
|
||||||
with source_repo.as_cwd():
|
|
||||||
subprocess.check_call(('git', 'init'))
|
|
||||||
os.symlink(TEST_SYMLINK_TARGET, TEST_SYMLINK)
|
|
||||||
with open(TEST_FILE, 'w') as f:
|
|
||||||
print('some random content', file=f)
|
|
||||||
subprocess.check_call(('git', 'add', '.'))
|
|
||||||
git_commit('-m', 'initial')
|
|
||||||
assert b'120000 ' in subprocess.check_output(
|
|
||||||
('git', 'cat-file', '-p', 'HEAD^{tree}'),
|
|
||||||
)
|
|
||||||
subprocess.check_call(
|
|
||||||
('git', '-c', 'core.symlinks=false', 'clone', source_repo, test_repo),
|
|
||||||
)
|
|
||||||
with test_repo.as_cwd():
|
|
||||||
subprocess.check_call(
|
|
||||||
('git', 'config', '--local', 'core.symlinks', 'true'),
|
|
||||||
)
|
|
||||||
subprocess.check_call(('git', 'mv', TEST_FILE, TEST_FILE_RENAMED))
|
|
||||||
assert not os.path.islink(test_repo.join(TEST_SYMLINK))
|
|
||||||
yield test_repo
|
|
||||||
|
|
||||||
|
|
||||||
def test_find_destroyed_symlinks(repo_with_destroyed_symlink):
|
|
||||||
with repo_with_destroyed_symlink.as_cwd():
|
|
||||||
assert find_destroyed_symlinks([]) == []
|
|
||||||
assert main([]) == 0
|
|
||||||
|
|
||||||
subprocess.check_call(('git', 'add', TEST_SYMLINK))
|
|
||||||
assert find_destroyed_symlinks([TEST_SYMLINK]) == [TEST_SYMLINK]
|
|
||||||
assert find_destroyed_symlinks([]) == []
|
|
||||||
assert main([]) == 0
|
|
||||||
assert find_destroyed_symlinks([TEST_FILE_RENAMED, TEST_FILE]) == []
|
|
||||||
ALL_STAGED = [TEST_SYMLINK, TEST_FILE_RENAMED]
|
|
||||||
assert find_destroyed_symlinks(ALL_STAGED) == [TEST_SYMLINK]
|
|
||||||
assert main(ALL_STAGED) != 0
|
|
||||||
|
|
||||||
with open(TEST_SYMLINK, 'a') as f:
|
|
||||||
print(file=f) # add trailing newline
|
|
||||||
subprocess.check_call(['git', 'add', TEST_SYMLINK])
|
|
||||||
assert find_destroyed_symlinks(ALL_STAGED) == [TEST_SYMLINK]
|
|
||||||
assert main(ALL_STAGED) != 0
|
|
||||||
|
|
||||||
with open(TEST_SYMLINK, 'w') as f:
|
|
||||||
print('0' * len(TEST_SYMLINK_TARGET), file=f)
|
|
||||||
subprocess.check_call(('git', 'add', TEST_SYMLINK))
|
|
||||||
assert find_destroyed_symlinks(ALL_STAGED) == []
|
|
||||||
assert main(ALL_STAGED) == 0
|
|
||||||
|
|
||||||
with open(TEST_SYMLINK, 'w') as f:
|
|
||||||
print('0' * (len(TEST_SYMLINK_TARGET) + 3), file=f)
|
|
||||||
subprocess.check_call(('git', 'add', TEST_SYMLINK))
|
|
||||||
assert find_destroyed_symlinks(ALL_STAGED) == []
|
|
||||||
assert main(ALL_STAGED) == 0
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
from pre_commit_hooks.detect_aws_credentials import get_aws_cred_files_from_env
|
from pre_commit_hooks.detect_aws_credentials import get_aws_cred_files_from_env
|
||||||
from pre_commit_hooks.detect_aws_credentials import get_aws_secrets_from_env
|
from pre_commit_hooks.detect_aws_credentials import get_aws_secrets_from_env
|
||||||
|
|
@ -15,15 +12,15 @@ from testing.util import get_resource_path
|
||||||
('env_vars', 'values'),
|
('env_vars', 'values'),
|
||||||
(
|
(
|
||||||
({}, set()),
|
({}, set()),
|
||||||
({'AWS_PLACEHOLDER_KEY': '/foo'}, set()),
|
({'AWS_DUMMY_KEY': '/foo'}, set()),
|
||||||
({'AWS_CONFIG_FILE': '/foo'}, {'/foo'}),
|
({'AWS_CONFIG_FILE': '/foo'}, {'/foo'}),
|
||||||
({'AWS_CREDENTIAL_FILE': '/foo'}, {'/foo'}),
|
({'AWS_CREDENTIAL_FILE': '/foo'}, {'/foo'}),
|
||||||
({'AWS_SHARED_CREDENTIALS_FILE': '/foo'}, {'/foo'}),
|
({'AWS_SHARED_CREDENTIALS_FILE': '/foo'}, {'/foo'}),
|
||||||
({'BOTO_CONFIG': '/foo'}, {'/foo'}),
|
({'BOTO_CONFIG': '/foo'}, {'/foo'}),
|
||||||
({'AWS_PLACEHOLDER_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar'}, {'/bar'}),
|
({'AWS_DUMMY_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar'}, {'/bar'}),
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
'AWS_PLACEHOLDER_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar',
|
'AWS_DUMMY_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar',
|
||||||
'AWS_CREDENTIAL_FILE': '/baz',
|
'AWS_CREDENTIAL_FILE': '/baz',
|
||||||
},
|
},
|
||||||
{'/bar', '/baz'},
|
{'/bar', '/baz'},
|
||||||
|
|
@ -46,16 +43,11 @@ def test_get_aws_credentials_file_from_env(env_vars, values):
|
||||||
('env_vars', 'values'),
|
('env_vars', 'values'),
|
||||||
(
|
(
|
||||||
({}, set()),
|
({}, set()),
|
||||||
({'AWS_PLACEHOLDER_KEY': 'foo'}, set()),
|
({'AWS_DUMMY_KEY': 'foo'}, set()),
|
||||||
({'AWS_SECRET_ACCESS_KEY': 'foo'}, {'foo'}),
|
({'AWS_SECRET_ACCESS_KEY': 'foo'}, {'foo'}),
|
||||||
({'AWS_SECURITY_TOKEN': 'foo'}, {'foo'}),
|
({'AWS_SECURITY_TOKEN': 'foo'}, {'foo'}),
|
||||||
({'AWS_SESSION_TOKEN': 'foo'}, {'foo'}),
|
({'AWS_SESSION_TOKEN': 'foo'}, {'foo'}),
|
||||||
({'AWS_SESSION_TOKEN': ''}, set()),
|
({'AWS_DUMMY_KEY': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar'}, {'bar'}),
|
||||||
({'AWS_SESSION_TOKEN': 'foo', 'AWS_SECURITY_TOKEN': ''}, {'foo'}),
|
|
||||||
(
|
|
||||||
{'AWS_PLACEHOLDER_KEY': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar'},
|
|
||||||
{'bar'},
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
{'AWS_SECRET_ACCESS_KEY': 'foo', 'AWS_SECURITY_TOKEN': 'bar'},
|
{'AWS_SECRET_ACCESS_KEY': 'foo', 'AWS_SECURITY_TOKEN': 'bar'},
|
||||||
{'foo', 'bar'},
|
{'foo', 'bar'},
|
||||||
|
|
@ -123,19 +115,6 @@ def test_detect_aws_credentials(filename, expected_retval):
|
||||||
assert ret == expected_retval
|
assert ret == expected_retval
|
||||||
|
|
||||||
|
|
||||||
def test_allows_arbitrarily_encoded_files(tmpdir):
|
|
||||||
src_ini = tmpdir.join('src.ini')
|
|
||||||
src_ini.write(
|
|
||||||
'[default]\n'
|
|
||||||
'aws_access_key_id=AKIASDFASDF\n'
|
|
||||||
'aws_secret_Access_key=9018asdf23908190238123\n',
|
|
||||||
)
|
|
||||||
arbitrary_encoding = tmpdir.join('f')
|
|
||||||
arbitrary_encoding.write_binary(b'\x12\x9a\xe2\xf2')
|
|
||||||
ret = main((str(arbitrary_encoding), '--credentials-file', str(src_ini)))
|
|
||||||
assert ret == 0
|
|
||||||
|
|
||||||
|
|
||||||
@patch('pre_commit_hooks.detect_aws_credentials.get_aws_secrets_from_file')
|
@patch('pre_commit_hooks.detect_aws_credentials.get_aws_secrets_from_file')
|
||||||
@patch('pre_commit_hooks.detect_aws_credentials.get_aws_secrets_from_env')
|
@patch('pre_commit_hooks.detect_aws_credentials.get_aws_secrets_from_env')
|
||||||
def test_non_existent_credentials(mock_secrets_env, mock_secrets_file, capsys):
|
def test_non_existent_credentials(mock_secrets_env, mock_secrets_file, capsys):
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks.detect_private_key import main
|
from pre_commit_hooks.detect_private_key import main
|
||||||
|
|
@ -12,8 +10,6 @@ TESTS = (
|
||||||
(b'-----BEGIN OPENSSH PRIVATE KEY-----', 1),
|
(b'-----BEGIN OPENSSH PRIVATE KEY-----', 1),
|
||||||
(b'PuTTY-User-Key-File-2: ssh-rsa', 1),
|
(b'PuTTY-User-Key-File-2: ssh-rsa', 1),
|
||||||
(b'---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----', 1),
|
(b'---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----', 1),
|
||||||
(b'-----BEGIN ENCRYPTED PRIVATE KEY-----', 1),
|
|
||||||
(b'-----BEGIN OpenVPN Static key V1-----', 1),
|
|
||||||
(b'ssh-rsa DATA', 0),
|
(b'ssh-rsa DATA', 0),
|
||||||
(b'ssh-dsa DATA', 0),
|
(b'ssh-dsa DATA', 0),
|
||||||
# Some arbitrary binary data
|
# Some arbitrary binary data
|
||||||
|
|
@ -25,4 +21,4 @@ TESTS = (
|
||||||
def test_main(input_s, expected_retval, tmpdir):
|
def test_main(input_s, expected_retval, tmpdir):
|
||||||
path = tmpdir.join('file.txt')
|
path = tmpdir.join('file.txt')
|
||||||
path.write_binary(input_s)
|
path.write_binary(input_s)
|
||||||
assert main([str(path)]) == expected_retval
|
assert main([path.strpath]) == expected_retval
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -37,7 +35,7 @@ def test_integration(input_s, expected_retval, output, tmpdir):
|
||||||
path = tmpdir.join('file.txt')
|
path = tmpdir.join('file.txt')
|
||||||
path.write_binary(input_s)
|
path.write_binary(input_s)
|
||||||
|
|
||||||
ret = main([str(path)])
|
ret = main([path.strpath])
|
||||||
file_output = path.read_binary()
|
file_output = path.read_binary()
|
||||||
|
|
||||||
assert file_output == output
|
assert file_output == output
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks.file_contents_sorter import FAIL
|
from pre_commit_hooks.file_contents_sorter import FAIL
|
||||||
|
|
@ -8,93 +6,28 @@ from pre_commit_hooks.file_contents_sorter import PASS
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
('input_s', 'argv', 'expected_retval', 'output'),
|
('input_s', 'expected_retval', 'output'),
|
||||||
(
|
(
|
||||||
(b'', [], PASS, b''),
|
(b'', FAIL, b'\n'),
|
||||||
(b'\n', [], FAIL, b''),
|
(b'lonesome\n', PASS, b'lonesome\n'),
|
||||||
(b'\n\n', [], FAIL, b''),
|
(b'missing_newline', FAIL, b'missing_newline\n'),
|
||||||
(b'lonesome\n', [], PASS, b'lonesome\n'),
|
(b'newline\nmissing', FAIL, b'missing\nnewline\n'),
|
||||||
(b'missing_newline', [], FAIL, b'missing_newline\n'),
|
(b'missing\nnewline', FAIL, b'missing\nnewline\n'),
|
||||||
(b'newline\nmissing', [], FAIL, b'missing\nnewline\n'),
|
(b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'),
|
||||||
(b'missing\nnewline', [], FAIL, b'missing\nnewline\n'),
|
(b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'),
|
||||||
(b'alpha\nbeta\n', [], PASS, b'alpha\nbeta\n'),
|
(b'C\nc\n', PASS, b'C\nc\n'),
|
||||||
(b'beta\nalpha\n', [], FAIL, b'alpha\nbeta\n'),
|
(b'c\nC\n', FAIL, b'C\nc\n'),
|
||||||
(b'C\nc\n', [], PASS, b'C\nc\n'),
|
(b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'),
|
||||||
(b'c\nC\n', [], FAIL, b'C\nc\n'),
|
(b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'),
|
||||||
(b'mag ical \n tre vor\n', [], FAIL, b' tre vor\nmag ical \n'),
|
(b'extra\n\n\nwhitespace\n', FAIL, b'extra\nwhitespace\n'),
|
||||||
(b'@\n-\n_\n#\n', [], FAIL, b'#\n-\n@\n_\n'),
|
(b'whitespace\n\n\nextra\n', FAIL, b'extra\nwhitespace\n'),
|
||||||
(b'extra\n\n\nwhitespace\n', [], FAIL, b'extra\nwhitespace\n'),
|
|
||||||
(b'whitespace\n\n\nextra\n', [], FAIL, b'extra\nwhitespace\n'),
|
|
||||||
(
|
|
||||||
b'fee\nFie\nFoe\nfum\n',
|
|
||||||
[],
|
|
||||||
FAIL,
|
|
||||||
b'Fie\nFoe\nfee\nfum\n',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
b'Fie\nFoe\nfee\nfum\n',
|
|
||||||
[],
|
|
||||||
PASS,
|
|
||||||
b'Fie\nFoe\nfee\nfum\n',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
b'fee\nFie\nFoe\nfum\n',
|
|
||||||
['--ignore-case'],
|
|
||||||
PASS,
|
|
||||||
b'fee\nFie\nFoe\nfum\n',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
b'Fie\nFoe\nfee\nfum\n',
|
|
||||||
['--ignore-case'],
|
|
||||||
FAIL,
|
|
||||||
b'fee\nFie\nFoe\nfum\n',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
b'Fie\nFoe\nfee\nfee\nfum\n',
|
|
||||||
['--ignore-case'],
|
|
||||||
FAIL,
|
|
||||||
b'fee\nfee\nFie\nFoe\nfum\n',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
b'Fie\nFoe\nfee\nfum\n',
|
|
||||||
['--unique'],
|
|
||||||
PASS,
|
|
||||||
b'Fie\nFoe\nfee\nfum\n',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
b'Fie\nFie\nFoe\nfee\nfum\n',
|
|
||||||
['--unique'],
|
|
||||||
FAIL,
|
|
||||||
b'Fie\nFoe\nfee\nfum\n',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_integration(input_s, argv, expected_retval, output, tmpdir):
|
def test_integration(input_s, expected_retval, output, tmpdir):
|
||||||
path = tmpdir.join('file.txt')
|
path = tmpdir.join('file.txt')
|
||||||
path.write_binary(input_s)
|
path.write_binary(input_s)
|
||||||
|
|
||||||
output_retval = main([str(path)] + argv)
|
output_retval = main([path.strpath])
|
||||||
|
|
||||||
assert path.read_binary() == output
|
assert path.read_binary() == output
|
||||||
assert output_retval == expected_retval
|
assert output_retval == expected_retval
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
('input_s', 'argv'),
|
|
||||||
(
|
|
||||||
(
|
|
||||||
b'fee\nFie\nFoe\nfum\n',
|
|
||||||
['--unique', '--ignore-case'],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
b'fee\nfee\nFie\nFoe\nfum\n',
|
|
||||||
['--unique', '--ignore-case'],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_integration_invalid_args(input_s, argv, tmpdir):
|
|
||||||
path = tmpdir.join('file.txt')
|
|
||||||
path.write_binary(input_s)
|
|
||||||
|
|
||||||
with pytest.raises(SystemExit):
|
|
||||||
main([str(path)] + argv)
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pre_commit_hooks import fix_byte_order_marker
|
|
||||||
|
|
||||||
|
|
||||||
def test_failure(tmpdir):
|
|
||||||
f = tmpdir.join('f.txt')
|
|
||||||
f.write_text('ohai', encoding='utf-8-sig')
|
|
||||||
assert fix_byte_order_marker.main((str(f),)) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_success(tmpdir):
|
|
||||||
f = tmpdir.join('f.txt')
|
|
||||||
f.write_text('ohai', encoding='utf-8')
|
|
||||||
assert fix_byte_order_marker.main((str(f),)) == 0
|
|
||||||
165
tests/fix_encoding_pragma_test.py
Normal file
165
tests/fix_encoding_pragma_test.py
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import io
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from pre_commit_hooks.fix_encoding_pragma import _normalize_pragma
|
||||||
|
from pre_commit_hooks.fix_encoding_pragma import fix_encoding_pragma
|
||||||
|
from pre_commit_hooks.fix_encoding_pragma import main
|
||||||
|
|
||||||
|
|
||||||
|
def test_integration_inserting_pragma(tmpdir):
|
||||||
|
path = tmpdir.join('foo.py')
|
||||||
|
path.write_binary(b'import httplib\n')
|
||||||
|
|
||||||
|
assert main((path.strpath,)) == 1
|
||||||
|
|
||||||
|
assert path.read_binary() == (
|
||||||
|
b'# -*- coding: utf-8 -*-\n'
|
||||||
|
b'import httplib\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_integration_ok(tmpdir):
|
||||||
|
path = tmpdir.join('foo.py')
|
||||||
|
path.write_binary(b'# -*- coding: utf-8 -*-\nx = 1\n')
|
||||||
|
assert main((path.strpath,)) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_integration_remove(tmpdir):
|
||||||
|
path = tmpdir.join('foo.py')
|
||||||
|
path.write_binary(b'# -*- coding: utf-8 -*-\nx = 1\n')
|
||||||
|
|
||||||
|
assert main((path.strpath, '--remove')) == 1
|
||||||
|
|
||||||
|
assert path.read_binary() == b'x = 1\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_integration_remove_ok(tmpdir):
|
||||||
|
path = tmpdir.join('foo.py')
|
||||||
|
path.write_binary(b'x = 1\n')
|
||||||
|
assert main((path.strpath, '--remove')) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'input_str',
|
||||||
|
(
|
||||||
|
b'',
|
||||||
|
(
|
||||||
|
b'# -*- coding: utf-8 -*-\n'
|
||||||
|
b'x = 1\n'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
b'#!/usr/bin/env python\n'
|
||||||
|
b'# -*- coding: utf-8 -*-\n'
|
||||||
|
b'foo = "bar"\n'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_ok_inputs(input_str):
|
||||||
|
bytesio = io.BytesIO(input_str)
|
||||||
|
assert fix_encoding_pragma(bytesio) == 0
|
||||||
|
bytesio.seek(0)
|
||||||
|
assert bytesio.read() == input_str
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
('input_str', 'output'),
|
||||||
|
(
|
||||||
|
(
|
||||||
|
b'import httplib\n',
|
||||||
|
b'# -*- coding: utf-8 -*-\n'
|
||||||
|
b'import httplib\n',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
b'#!/usr/bin/env python\n'
|
||||||
|
b'x = 1\n',
|
||||||
|
b'#!/usr/bin/env python\n'
|
||||||
|
b'# -*- coding: utf-8 -*-\n'
|
||||||
|
b'x = 1\n',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
b'#coding=utf-8\n'
|
||||||
|
b'x = 1\n',
|
||||||
|
b'# -*- coding: utf-8 -*-\n'
|
||||||
|
b'x = 1\n',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
b'#!/usr/bin/env python\n'
|
||||||
|
b'#coding=utf8\n'
|
||||||
|
b'x = 1\n',
|
||||||
|
b'#!/usr/bin/env python\n'
|
||||||
|
b'# -*- coding: utf-8 -*-\n'
|
||||||
|
b'x = 1\n',
|
||||||
|
),
|
||||||
|
# These should each get truncated
|
||||||
|
(b'#coding: utf-8\n', b''),
|
||||||
|
(b'# -*- coding: utf-8 -*-\n', b''),
|
||||||
|
(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)
|
||||||
|
assert fix_encoding_pragma(bytesio) == 1
|
||||||
|
bytesio.seek(0)
|
||||||
|
assert bytesio.read() == output
|
||||||
|
|
||||||
|
|
||||||
|
def test_ok_input_alternate_pragma():
|
||||||
|
input_s = b'# coding: utf-8\nx = 1\n'
|
||||||
|
bytesio = io.BytesIO(input_s)
|
||||||
|
ret = fix_encoding_pragma(bytesio, expected_pragma=b'# coding: utf-8')
|
||||||
|
assert ret == 0
|
||||||
|
bytesio.seek(0)
|
||||||
|
assert bytesio.read() == input_s
|
||||||
|
|
||||||
|
|
||||||
|
def test_not_ok_input_alternate_pragma():
|
||||||
|
bytesio = io.BytesIO(b'x = 1\n')
|
||||||
|
ret = fix_encoding_pragma(bytesio, expected_pragma=b'# coding: utf-8')
|
||||||
|
assert ret == 1
|
||||||
|
bytesio.seek(0)
|
||||||
|
assert bytesio.read() == b'# coding: utf-8\nx = 1\n'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
('input_s', 'expected'),
|
||||||
|
(
|
||||||
|
# Python 2 cli parameters are bytes
|
||||||
|
(b'# coding: utf-8', b'# coding: utf-8'),
|
||||||
|
# Python 3 cli parameters are text
|
||||||
|
('# coding: utf-8', b'# coding: utf-8'),
|
||||||
|
# trailing whitespace
|
||||||
|
('# coding: utf-8\n', b'# coding: utf-8'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_normalize_pragma(input_s, expected):
|
||||||
|
assert _normalize_pragma(input_s) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_integration_alternate_pragma(tmpdir, capsys):
|
||||||
|
f = tmpdir.join('f.py')
|
||||||
|
f.write('x = 1\n')
|
||||||
|
|
||||||
|
pragma = '# coding: utf-8'
|
||||||
|
assert main((f.strpath, '--pragma', pragma)) == 1
|
||||||
|
assert f.read() == '# coding: utf-8\nx = 1\n'
|
||||||
|
out, _ = capsys.readouterr()
|
||||||
|
assert out == 'Added `# coding: utf-8` to {}\n'.format(f.strpath)
|
||||||
|
|
||||||
|
|
||||||
|
def test_crlf_ok(tmpdir):
|
||||||
|
f = tmpdir.join('f.py')
|
||||||
|
f.write_binary(b'# -*- coding: utf-8 -*-\r\nx = 1\r\n')
|
||||||
|
assert not main((f.strpath,))
|
||||||
|
|
||||||
|
|
||||||
|
def test_crfl_adds(tmpdir):
|
||||||
|
f = tmpdir.join('f.py')
|
||||||
|
f.write_binary(b'x = 1\r\n')
|
||||||
|
assert main((f.strpath,))
|
||||||
|
assert f.read_binary() == b'# -*- coding: utf-8 -*-\r\nx = 1\r\n'
|
||||||
|
|
@ -1,22 +1,24 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks.forbid_new_submodules import main
|
from pre_commit_hooks.forbid_new_submodules import main
|
||||||
from testing.util import git_commit
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def git_dir_with_git_dir(tmpdir):
|
def git_dir_with_git_dir(tmpdir):
|
||||||
with tmpdir.as_cwd():
|
with tmpdir.as_cwd():
|
||||||
subprocess.check_call(('git', 'init', '.'))
|
subprocess.check_call(('git', 'init', '.'))
|
||||||
git_commit('--allow-empty', '-m', 'init')
|
subprocess.check_call((
|
||||||
|
'git', 'commit', '-m', 'init', '--allow-empty', '--no-gpg-sign',
|
||||||
|
))
|
||||||
subprocess.check_call(('git', 'init', 'foo'))
|
subprocess.check_call(('git', 'init', 'foo'))
|
||||||
git_commit('--allow-empty', '-m', 'init', cwd=str(tmpdir.join('foo')))
|
subprocess.check_call(
|
||||||
|
('git', 'commit', '-m', 'init', '--allow-empty', '--no-gpg-sign'),
|
||||||
|
cwd=tmpdir.join('foo').strpath,
|
||||||
|
)
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,24 +33,7 @@ def git_dir_with_git_dir(tmpdir):
|
||||||
)
|
)
|
||||||
def test_main_new_submodule(git_dir_with_git_dir, capsys, cmd):
|
def test_main_new_submodule(git_dir_with_git_dir, capsys, cmd):
|
||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
assert main(('random_non-related_file',)) == 0
|
assert main() == 1
|
||||||
assert main(('foo',)) == 1
|
|
||||||
out, _ = capsys.readouterr()
|
|
||||||
assert out.startswith('foo: new submodule introduced\n')
|
|
||||||
|
|
||||||
|
|
||||||
def test_main_new_submodule_committed(git_dir_with_git_dir, capsys):
|
|
||||||
rev_parse_cmd = ('git', 'rev-parse', 'HEAD')
|
|
||||||
from_ref = subprocess.check_output(rev_parse_cmd).decode().strip()
|
|
||||||
subprocess.check_call(('git', 'submodule', 'add', './foo'))
|
|
||||||
git_commit('-m', 'new submodule')
|
|
||||||
to_ref = subprocess.check_output(rev_parse_cmd).decode().strip()
|
|
||||||
with mock.patch.dict(
|
|
||||||
os.environ,
|
|
||||||
{'PRE_COMMIT_FROM_REF': from_ref, 'PRE_COMMIT_TO_REF': to_ref},
|
|
||||||
):
|
|
||||||
assert main(('random_non-related_file',)) == 0
|
|
||||||
assert main(('foo',)) == 1
|
|
||||||
out, _ = capsys.readouterr()
|
out, _ = capsys.readouterr()
|
||||||
assert out.startswith('foo: new submodule introduced\n')
|
assert out.startswith('foo: new submodule introduced\n')
|
||||||
|
|
||||||
|
|
@ -56,4 +41,4 @@ def test_main_new_submodule_committed(git_dir_with_git_dir, capsys):
|
||||||
def test_main_no_new_submodule(git_dir_with_git_dir):
|
def test_main_no_new_submodule(git_dir_with_git_dir):
|
||||||
open('test.py', 'a+').close()
|
open('test.py', 'a+').close()
|
||||||
subprocess.check_call(('git', 'add', 'test.py'))
|
subprocess.check_call(('git', 'add', 'test.py'))
|
||||||
assert main(('test.py',)) == 0
|
assert main() == 0
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -27,7 +28,7 @@ from pre_commit_hooks.mixed_line_ending import main
|
||||||
def test_mixed_line_ending_fixes_auto(input_s, output, tmpdir):
|
def test_mixed_line_ending_fixes_auto(input_s, output, tmpdir):
|
||||||
path = tmpdir.join('file.txt')
|
path = tmpdir.join('file.txt')
|
||||||
path.write_binary(input_s)
|
path.write_binary(input_s)
|
||||||
ret = main((str(path),))
|
ret = main((path.strpath,))
|
||||||
|
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
assert path.read_binary() == output
|
assert path.read_binary() == output
|
||||||
|
|
@ -36,7 +37,7 @@ def test_mixed_line_ending_fixes_auto(input_s, output, tmpdir):
|
||||||
def test_non_mixed_no_newline_end_of_file(tmpdir):
|
def test_non_mixed_no_newline_end_of_file(tmpdir):
|
||||||
path = tmpdir.join('f.txt')
|
path = tmpdir.join('f.txt')
|
||||||
path.write_binary(b'foo\nbar\nbaz')
|
path.write_binary(b'foo\nbar\nbaz')
|
||||||
assert not main((str(path),))
|
assert not main((path.strpath,))
|
||||||
# the hook *could* fix the end of the file, but leaves it alone
|
# the hook *could* fix the end of the file, but leaves it alone
|
||||||
# this is mostly to document the current behaviour
|
# this is mostly to document the current behaviour
|
||||||
assert path.read_binary() == b'foo\nbar\nbaz'
|
assert path.read_binary() == b'foo\nbar\nbaz'
|
||||||
|
|
@ -45,7 +46,7 @@ def test_non_mixed_no_newline_end_of_file(tmpdir):
|
||||||
def test_mixed_no_newline_end_of_file(tmpdir):
|
def test_mixed_no_newline_end_of_file(tmpdir):
|
||||||
path = tmpdir.join('f.txt')
|
path = tmpdir.join('f.txt')
|
||||||
path.write_binary(b'foo\r\nbar\nbaz')
|
path.write_binary(b'foo\r\nbar\nbaz')
|
||||||
assert main((str(path),))
|
assert main((path.strpath,))
|
||||||
# the hook rewrites the end of the file, this is slightly inconsistent
|
# the hook rewrites the end of the file, this is slightly inconsistent
|
||||||
# with the non-mixed case but I think this is the better behaviour
|
# with the non-mixed case but I think this is the better behaviour
|
||||||
# this is mostly to document the current behaviour
|
# this is mostly to document the current behaviour
|
||||||
|
|
@ -68,7 +69,7 @@ def test_mixed_no_newline_end_of_file(tmpdir):
|
||||||
def test_line_endings_ok(fix_option, input_s, tmpdir, capsys):
|
def test_line_endings_ok(fix_option, input_s, tmpdir, capsys):
|
||||||
path = tmpdir.join('input.txt')
|
path = tmpdir.join('input.txt')
|
||||||
path.write_binary(input_s)
|
path.write_binary(input_s)
|
||||||
ret = main((fix_option, str(path)))
|
ret = main((fix_option, path.strpath))
|
||||||
|
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
assert path.read_binary() == input_s
|
assert path.read_binary() == input_s
|
||||||
|
|
@ -80,29 +81,29 @@ def test_no_fix_does_not_modify(tmpdir, capsys):
|
||||||
path = tmpdir.join('input.txt')
|
path = tmpdir.join('input.txt')
|
||||||
contents = b'foo\r\nbar\rbaz\nwomp\n'
|
contents = b'foo\r\nbar\rbaz\nwomp\n'
|
||||||
path.write_binary(contents)
|
path.write_binary(contents)
|
||||||
ret = main(('--fix=no', str(path)))
|
ret = main(('--fix=no', path.strpath))
|
||||||
|
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
assert path.read_binary() == contents
|
assert path.read_binary() == contents
|
||||||
out, _ = capsys.readouterr()
|
out, _ = capsys.readouterr()
|
||||||
assert out == f'{path}: mixed line endings\n'
|
assert out == '{}: mixed line endings\n'.format(path)
|
||||||
|
|
||||||
|
|
||||||
def test_fix_lf(tmpdir, capsys):
|
def test_fix_lf(tmpdir, capsys):
|
||||||
path = tmpdir.join('input.txt')
|
path = tmpdir.join('input.txt')
|
||||||
path.write_binary(b'foo\r\nbar\rbaz\n')
|
path.write_binary(b'foo\r\nbar\rbaz\n')
|
||||||
ret = main(('--fix=lf', str(path)))
|
ret = main(('--fix=lf', path.strpath))
|
||||||
|
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
assert path.read_binary() == b'foo\nbar\nbaz\n'
|
assert path.read_binary() == b'foo\nbar\nbaz\n'
|
||||||
out, _ = capsys.readouterr()
|
out, _ = capsys.readouterr()
|
||||||
assert out == f'{path}: fixed mixed line endings\n'
|
assert out == '{}: fixed mixed line endings\n'.format(path)
|
||||||
|
|
||||||
|
|
||||||
def test_fix_crlf(tmpdir):
|
def test_fix_crlf(tmpdir):
|
||||||
path = tmpdir.join('input.txt')
|
path = tmpdir.join('input.txt')
|
||||||
path.write_binary(b'foo\r\nbar\rbaz\n')
|
path.write_binary(b'foo\r\nbar\rbaz\n')
|
||||||
ret = main(('--fix=crlf', str(path)))
|
ret = main(('--fix=crlf', path.strpath))
|
||||||
|
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
assert path.read_binary() == b'foo\r\nbar\r\nbaz\r\n'
|
assert path.read_binary() == b'foo\r\nbar\r\nbaz\r\n'
|
||||||
|
|
@ -112,7 +113,7 @@ def test_fix_lf_all_crlf(tmpdir):
|
||||||
"""Regression test for #239"""
|
"""Regression test for #239"""
|
||||||
path = tmpdir.join('input.txt')
|
path = tmpdir.join('input.txt')
|
||||||
path.write_binary(b'foo\r\nbar\r\n')
|
path.write_binary(b'foo\r\nbar\r\n')
|
||||||
ret = main(('--fix=lf', str(path)))
|
ret = main(('--fix=lf', path.strpath))
|
||||||
|
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
assert path.read_binary() == b'foo\nbar\n'
|
assert path.read_binary() == b'foo\nbar\n'
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks.no_commit_to_branch import is_on_branch
|
from pre_commit_hooks.no_commit_to_branch import is_on_branch
|
||||||
from pre_commit_hooks.no_commit_to_branch import main
|
from pre_commit_hooks.no_commit_to_branch import main
|
||||||
from pre_commit_hooks.util import cmd_output
|
from pre_commit_hooks.util import cmd_output
|
||||||
from testing.util import git_commit
|
|
||||||
|
|
||||||
|
|
||||||
def test_other_branch(temp_git_dir):
|
def test_other_branch(temp_git_dir):
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
cmd_output('git', 'checkout', '-b', 'anotherbranch')
|
cmd_output('git', 'checkout', '-b', 'anotherbranch')
|
||||||
assert is_on_branch({'placeholder'}) is False
|
assert is_on_branch({'master'}) is False
|
||||||
|
|
||||||
|
|
||||||
def test_multi_branch(temp_git_dir):
|
def test_multi_branch(temp_git_dir):
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
cmd_output('git', 'checkout', '-b', 'another/branch')
|
cmd_output('git', 'checkout', '-b', 'another/branch')
|
||||||
assert is_on_branch({'placeholder'}) is False
|
assert is_on_branch({'master'}) is False
|
||||||
|
|
||||||
|
|
||||||
def test_multi_branch_fail(temp_git_dir):
|
def test_multi_branch_fail(temp_git_dir):
|
||||||
|
|
@ -26,10 +26,9 @@ def test_multi_branch_fail(temp_git_dir):
|
||||||
assert is_on_branch({'another/branch'}) is True
|
assert is_on_branch({'another/branch'}) is True
|
||||||
|
|
||||||
|
|
||||||
def test_exact_branch(temp_git_dir):
|
def test_master_branch(temp_git_dir):
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
cmd_output('git', 'checkout', '-b', 'branchname')
|
assert is_on_branch({'master'}) is True
|
||||||
assert is_on_branch({'branchname'}) is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_main_branch_call(temp_git_dir):
|
def test_main_branch_call(temp_git_dir):
|
||||||
|
|
@ -51,11 +50,11 @@ def test_branch_pattern_fail(temp_git_dir):
|
||||||
assert is_on_branch(set(), {'another/.*'}) is True
|
assert is_on_branch(set(), {'another/.*'}) is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('branch_name', ('somebranch', 'another/branch'))
|
@pytest.mark.parametrize('branch_name', ('master', 'another/branch'))
|
||||||
def test_branch_pattern_multiple_branches_fail(temp_git_dir, branch_name):
|
def test_branch_pattern_multiple_branches_fail(temp_git_dir, branch_name):
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
cmd_output('git', 'checkout', '-b', branch_name)
|
cmd_output('git', 'checkout', '-b', branch_name)
|
||||||
assert main(('--branch', 'somebranch', '--pattern', 'another/.*'))
|
assert main(('--branch', 'master', '--pattern', 'another/.*'))
|
||||||
|
|
||||||
|
|
||||||
def test_main_default_call(temp_git_dir):
|
def test_main_default_call(temp_git_dir):
|
||||||
|
|
@ -66,15 +65,8 @@ def test_main_default_call(temp_git_dir):
|
||||||
|
|
||||||
def test_not_on_a_branch(temp_git_dir):
|
def test_not_on_a_branch(temp_git_dir):
|
||||||
with temp_git_dir.as_cwd():
|
with temp_git_dir.as_cwd():
|
||||||
git_commit('--allow-empty', '-m1')
|
cmd_output('git', 'commit', '--no-gpg-sign', '--allow-empty', '-m1')
|
||||||
head = cmd_output('git', 'rev-parse', 'HEAD').strip()
|
head = cmd_output('git', 'rev-parse', 'HEAD').strip()
|
||||||
cmd_output('git', 'checkout', head)
|
cmd_output('git', 'checkout', head)
|
||||||
# we're not on a branch!
|
# we're not on a branch!
|
||||||
assert main(()) == 0
|
assert main(()) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('branch_name', ('master', 'main'))
|
|
||||||
def test_default_branch_names(temp_git_dir, branch_name):
|
|
||||||
with temp_git_dir.as_cwd():
|
|
||||||
cmd_output('git', 'checkout', '-b', branch_name)
|
|
||||||
assert main(()) == 1
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from six import PY2
|
||||||
|
|
||||||
from pre_commit_hooks.pretty_format_json import main
|
from pre_commit_hooks.pretty_format_json import main
|
||||||
from pre_commit_hooks.pretty_format_json import parse_num_to_int
|
from pre_commit_hooks.pretty_format_json import parse_num_to_int
|
||||||
|
|
@ -43,6 +42,7 @@ def test_unsorted_main(filename, expected_retval):
|
||||||
assert ret == expected_retval
|
assert ret == expected_retval
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(PY2, reason='Requires Python3')
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
('filename', 'expected_retval'), (
|
('filename', 'expected_retval'), (
|
||||||
('not_pretty_formatted_json.json', 1),
|
('not_pretty_formatted_json.json', 1),
|
||||||
|
|
@ -52,7 +52,7 @@ def test_unsorted_main(filename, expected_retval):
|
||||||
('tab_pretty_formatted_json.json', 0),
|
('tab_pretty_formatted_json.json', 0),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_tab_main(filename, expected_retval):
|
def test_tab_main(filename, expected_retval): # pragma: no cover
|
||||||
ret = main(['--indent', '\t', get_resource_path(filename)])
|
ret = main(['--indent', '\t', get_resource_path(filename)])
|
||||||
assert ret == expected_retval
|
assert ret == expected_retval
|
||||||
|
|
||||||
|
|
@ -69,37 +69,19 @@ def test_autofix_main(tmpdir):
|
||||||
srcfile = tmpdir.join('to_be_json_formatted.json')
|
srcfile = tmpdir.join('to_be_json_formatted.json')
|
||||||
shutil.copyfile(
|
shutil.copyfile(
|
||||||
get_resource_path('not_pretty_formatted_json.json'),
|
get_resource_path('not_pretty_formatted_json.json'),
|
||||||
str(srcfile),
|
srcfile.strpath,
|
||||||
)
|
)
|
||||||
|
|
||||||
# now launch the autofix on that file
|
# now launch the autofix on that file
|
||||||
ret = main(['--autofix', str(srcfile)])
|
ret = main(['--autofix', srcfile.strpath])
|
||||||
# it should have formatted it
|
# it should have formatted it
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
|
|
||||||
# file was formatted (shouldn't trigger linter again)
|
# file was formatted (shouldn't trigger linter again)
|
||||||
ret = main([str(srcfile)])
|
ret = main([srcfile.strpath])
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_main(tmpdir):
|
|
||||||
srcfile1 = tmpdir.join('not_valid_json.json')
|
|
||||||
srcfile1.write(
|
|
||||||
'{\n'
|
|
||||||
' // not json\n'
|
|
||||||
' "a": "b"\n'
|
|
||||||
'}',
|
|
||||||
)
|
|
||||||
srcfile2 = tmpdir.join('to_be_json_formatted.json')
|
|
||||||
srcfile2.write('{ "a": "b" }')
|
|
||||||
|
|
||||||
# it should have skipped the first file and formatted the second one
|
|
||||||
assert main(['--autofix', str(srcfile1), str(srcfile2)]) == 1
|
|
||||||
|
|
||||||
# confirm second file was formatted (shouldn't trigger linter again)
|
|
||||||
assert main([str(srcfile2)]) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_orderfile_get_pretty_format():
|
def test_orderfile_get_pretty_format():
|
||||||
ret = main((
|
ret = main((
|
||||||
'--top-keys=alist', get_resource_path('pretty_formatted_json.json'),
|
'--top-keys=alist', get_resource_path('pretty_formatted_json.json'),
|
||||||
|
|
@ -131,9 +113,9 @@ def test_diffing_output(capsys):
|
||||||
expected_retval = 1
|
expected_retval = 1
|
||||||
a = os.path.join('a', resource_path)
|
a = os.path.join('a', resource_path)
|
||||||
b = os.path.join('b', resource_path)
|
b = os.path.join('b', resource_path)
|
||||||
expected_out = f'''\
|
expected_out = '''\
|
||||||
--- {a}
|
--- {}
|
||||||
+++ {b}
|
+++ {}
|
||||||
@@ -1,6 +1,9 @@
|
@@ -1,6 +1,9 @@
|
||||||
{{
|
{{
|
||||||
- "foo":
|
- "foo":
|
||||||
|
|
@ -148,7 +130,7 @@ def test_diffing_output(capsys):
|
||||||
+ "blah": null,
|
+ "blah": null,
|
||||||
+ "foo": "bar"
|
+ "foo": "bar"
|
||||||
}}
|
}}
|
||||||
'''
|
'''.format(a, b)
|
||||||
actual_retval = main([resource_path])
|
actual_retval = main([resource_path])
|
||||||
actual_out, actual_err = capsys.readouterr()
|
actual_out, actual_err = capsys.readouterr()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import io
|
||||||
|
|
||||||
from pre_commit_hooks.check_yaml import yaml
|
from pre_commit_hooks.check_yaml import yaml
|
||||||
|
|
||||||
|
|
||||||
def test_readme_contains_all_hooks():
|
def test_readme_contains_all_hooks():
|
||||||
with open('README.md', encoding='UTF-8') as f:
|
with io.open('README.md', encoding='UTF-8') as f:
|
||||||
readme_contents = f.read()
|
readme_contents = f.read()
|
||||||
with open('.pre-commit-hooks.yaml', encoding='UTF-8') as f:
|
with io.open('.pre-commit-hooks.yaml', encoding='UTF-8') as f:
|
||||||
hooks = yaml.load(f)
|
hooks = yaml.load(f)
|
||||||
for hook in hooks:
|
for hook in hooks:
|
||||||
assert f'`{hook["id"]}`' in readme_contents
|
assert '`{}`'.format(hook['id']) in readme_contents
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from pre_commit_hooks.removed import main
|
|
||||||
|
|
||||||
|
|
||||||
def test_always_fails():
|
|
||||||
with pytest.raises(SystemExit) as excinfo:
|
|
||||||
main((
|
|
||||||
'autopep8-wrapper', 'autopep8',
|
|
||||||
'https://github.com/pre-commit/mirrors-autopep8',
|
|
||||||
'--foo', 'bar',
|
|
||||||
))
|
|
||||||
msg, = excinfo.value.args
|
|
||||||
assert msg == (
|
|
||||||
'`autopep8-wrapper` has been removed -- '
|
|
||||||
'use `autopep8` from https://github.com/pre-commit/mirrors-autopep8'
|
|
||||||
)
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks.requirements_txt_fixer import FAIL
|
from pre_commit_hooks.requirements_txt_fixer import FAIL
|
||||||
|
|
@ -32,47 +30,12 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement
|
||||||
),
|
),
|
||||||
(b'#comment\n\nfoo\nbar\n', FAIL, b'#comment\n\nbar\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'#comment\n\nbar\nfoo\n', PASS, b'#comment\n\nbar\nfoo\n'),
|
||||||
(
|
|
||||||
b'foo\n\t#comment with indent\nbar\n',
|
|
||||||
FAIL,
|
|
||||||
b'\t#comment with indent\nbar\nfoo\n',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
b'bar\n\t#comment with indent\nfoo\n',
|
|
||||||
PASS,
|
|
||||||
b'bar\n\t#comment with indent\nfoo\n',
|
|
||||||
),
|
|
||||||
(b'\nfoo\nbar\n', FAIL, b'bar\n\nfoo\n'),
|
(b'\nfoo\nbar\n', FAIL, b'bar\n\nfoo\n'),
|
||||||
(b'\nbar\nfoo\n', PASS, b'\nbar\nfoo\n'),
|
(b'\nbar\nfoo\n', PASS, b'\nbar\nfoo\n'),
|
||||||
(
|
(
|
||||||
b'pyramid-foo==1\npyramid>=2\n',
|
b'pyramid==1\npyramid-foo==2\n',
|
||||||
FAIL,
|
PASS,
|
||||||
b'pyramid>=2\npyramid-foo==1\n',
|
b'pyramid==1\npyramid-foo==2\n',
|
||||||
),
|
|
||||||
(
|
|
||||||
b'a==1\n'
|
|
||||||
b'c>=1\n'
|
|
||||||
b'bbbb!=1\n'
|
|
||||||
b'c-a>=1;python_version>="3.6"\n'
|
|
||||||
b'e>=2\n'
|
|
||||||
b'd>2\n'
|
|
||||||
b'g<2\n'
|
|
||||||
b'f<=2\n',
|
|
||||||
FAIL,
|
|
||||||
b'a==1\n'
|
|
||||||
b'bbbb!=1\n'
|
|
||||||
b'c>=1\n'
|
|
||||||
b'c-a>=1;python_version>="3.6"\n'
|
|
||||||
b'd>2\n'
|
|
||||||
b'e>=2\n'
|
|
||||||
b'f<=2\n'
|
|
||||||
b'g<2\n',
|
|
||||||
),
|
|
||||||
(b'a==1\nb==1\na==1\n', FAIL, b'a==1\nb==1\n'),
|
|
||||||
(
|
|
||||||
b'a==1\nb==1\n#comment about a\na==1\n',
|
|
||||||
FAIL,
|
|
||||||
b'#comment about a\na==1\nb==1\n',
|
|
||||||
),
|
),
|
||||||
(b'ocflib\nDjango\nPyMySQL\n', FAIL, b'Django\nocflib\nPyMySQL\n'),
|
(b'ocflib\nDjango\nPyMySQL\n', FAIL, b'Django\nocflib\nPyMySQL\n'),
|
||||||
(
|
(
|
||||||
|
|
@ -82,38 +45,18 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement
|
||||||
),
|
),
|
||||||
(b'bar\npkg-resources==0.0.0\nfoo\n', FAIL, b'bar\nfoo\n'),
|
(b'bar\npkg-resources==0.0.0\nfoo\n', FAIL, b'bar\nfoo\n'),
|
||||||
(b'foo\npkg-resources==0.0.0\nbar\n', FAIL, b'bar\nfoo\n'),
|
(b'foo\npkg-resources==0.0.0\nbar\n', FAIL, b'bar\nfoo\n'),
|
||||||
(b'bar\npkg_resources==0.0.0\nfoo\n', FAIL, b'bar\nfoo\n'),
|
|
||||||
(b'foo\npkg_resources==0.0.0\nbar\n', FAIL, b'bar\nfoo\n'),
|
|
||||||
(
|
(
|
||||||
b'git+ssh://git_url@tag#egg=ocflib\nDjango\nijk\n',
|
b'git+ssh://git_url@tag#egg=ocflib\nDjango\nijk\n',
|
||||||
FAIL,
|
FAIL,
|
||||||
b'Django\nijk\ngit+ssh://git_url@tag#egg=ocflib\n',
|
b'Django\nijk\ngit+ssh://git_url@tag#egg=ocflib\n',
|
||||||
),
|
),
|
||||||
(
|
|
||||||
b'b==1.0.0\n'
|
|
||||||
b'c=2.0.0 \\\n'
|
|
||||||
b' --hash=sha256:abcd\n'
|
|
||||||
b'a=3.0.0 \\\n'
|
|
||||||
b' --hash=sha256:a1b1c1d1',
|
|
||||||
FAIL,
|
|
||||||
b'a=3.0.0 \\\n'
|
|
||||||
b' --hash=sha256:a1b1c1d1\n'
|
|
||||||
b'b==1.0.0\n'
|
|
||||||
b'c=2.0.0 \\\n'
|
|
||||||
b' --hash=sha256:abcd\n',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
b'a=2.0.0 \\\n --hash=sha256:abcd\nb==1.0.0\n',
|
|
||||||
PASS,
|
|
||||||
b'a=2.0.0 \\\n --hash=sha256:abcd\nb==1.0.0\n',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_integration(input_s, expected_retval, output, tmpdir):
|
def test_integration(input_s, expected_retval, output, tmpdir):
|
||||||
path = tmpdir.join('file.txt')
|
path = tmpdir.join('file.txt')
|
||||||
path.write_binary(input_s)
|
path.write_binary(input_s)
|
||||||
|
|
||||||
output_retval = main([str(path)])
|
output_retval = main([path.strpath])
|
||||||
|
|
||||||
assert path.read_binary() == output
|
assert path.read_binary() == output
|
||||||
assert output_retval == expected_retval
|
assert output_retval == expected_retval
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
@ -41,14 +42,14 @@ TEST_SORTS = [
|
||||||
|
|
||||||
@pytest.mark.parametrize('bad_lines,good_lines,retval', TEST_SORTS)
|
@pytest.mark.parametrize('bad_lines,good_lines,retval', TEST_SORTS)
|
||||||
def test_integration_good_bad_lines(tmpdir, bad_lines, good_lines, retval):
|
def test_integration_good_bad_lines(tmpdir, bad_lines, good_lines, retval):
|
||||||
file_path = os.path.join(str(tmpdir), 'foo.yaml')
|
file_path = os.path.join(tmpdir.strpath, 'foo.yaml')
|
||||||
|
|
||||||
with open(file_path, 'w') as f:
|
with open(file_path, 'w') as f:
|
||||||
f.write('\n'.join(bad_lines) + '\n')
|
f.write('\n'.join(bad_lines) + '\n')
|
||||||
|
|
||||||
assert main([file_path]) == retval
|
assert main([file_path]) == retval
|
||||||
|
|
||||||
with open(file_path) as f:
|
with open(file_path, 'r') as f:
|
||||||
assert [line.rstrip() for line in f.readlines()] == good_lines
|
assert [line.rstrip() for line in f.readlines()] == good_lines
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
|
@ -37,12 +39,6 @@ TESTS = (
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
('"foo""bar"', "'foo''bar'", 1),
|
('"foo""bar"', "'foo''bar'", 1),
|
||||||
pytest.param(
|
|
||||||
"f'hello{\"world\"}'",
|
|
||||||
"f'hello{\"world\"}'",
|
|
||||||
0,
|
|
||||||
id='ignore nested fstrings',
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -50,7 +46,7 @@ TESTS = (
|
||||||
def test_rewrite(input_s, output, expected_retval, tmpdir):
|
def test_rewrite(input_s, output, expected_retval, tmpdir):
|
||||||
path = tmpdir.join('file.py')
|
path = tmpdir.join('file.py')
|
||||||
path.write(input_s)
|
path.write(input_s)
|
||||||
retval = main([str(path)])
|
retval = main([path.strpath])
|
||||||
assert path.read() == output
|
assert path.read() == output
|
||||||
assert retval == expected_retval
|
assert retval == expected_retval
|
||||||
|
|
||||||
|
|
@ -58,5 +54,5 @@ def test_rewrite(input_s, output, expected_retval, tmpdir):
|
||||||
def test_rewrite_crlf(tmpdir):
|
def test_rewrite_crlf(tmpdir):
|
||||||
f = tmpdir.join('f.py')
|
f = tmpdir.join('f.py')
|
||||||
f.write_binary(b'"foo"\r\n"bar"\r\n')
|
f.write_binary(b'"foo"\r\n"bar"\r\n')
|
||||||
assert main((str(f),))
|
assert main((f.strpath,))
|
||||||
assert f.read_binary() == b"'foo'\r\n'bar'\r\n"
|
assert f.read_binary() == b"'foo'\r\n'bar'\r\n"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pre_commit_hooks.tests_should_end_in_test import main
|
from pre_commit_hooks.tests_should_end_in_test import main
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -43,8 +41,3 @@ def test_main_not_django_fails():
|
||||||
def test_main_django_fails():
|
def test_main_django_fails():
|
||||||
ret = main(['--django', 'foo_test.py', 'test_bar.py', 'test_baz.py'])
|
ret = main(['--django', 'foo_test.py', 'test_bar.py', 'test_baz.py'])
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
|
|
||||||
|
|
||||||
def test_main_pytest_test_first():
|
|
||||||
assert main(['--pytest-test-first', 'test_foo.py']) == 0
|
|
||||||
assert main(['--pytest-test-first', 'foo_test.py']) == 1
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -15,14 +16,14 @@ from pre_commit_hooks.trailing_whitespace_fixer import main
|
||||||
def test_fixes_trailing_whitespace(input_s, expected, tmpdir):
|
def test_fixes_trailing_whitespace(input_s, expected, tmpdir):
|
||||||
path = tmpdir.join('file.md')
|
path = tmpdir.join('file.md')
|
||||||
path.write(input_s)
|
path.write(input_s)
|
||||||
assert main((str(path),)) == 1
|
assert main((path.strpath,)) == 1
|
||||||
assert path.read() == expected
|
assert path.read() == expected
|
||||||
|
|
||||||
|
|
||||||
def test_ok_no_newline_end_of_file(tmpdir):
|
def test_ok_no_newline_end_of_file(tmpdir):
|
||||||
filename = tmpdir.join('f')
|
filename = tmpdir.join('f')
|
||||||
filename.write_binary(b'foo\nbar')
|
filename.write_binary(b'foo\nbar')
|
||||||
ret = main((str(filename),))
|
ret = main((filename.strpath,))
|
||||||
assert filename.read_binary() == b'foo\nbar'
|
assert filename.read_binary() == b'foo\nbar'
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
|
|
||||||
|
|
@ -30,7 +31,7 @@ def test_ok_no_newline_end_of_file(tmpdir):
|
||||||
def test_ok_with_dos_line_endings(tmpdir):
|
def test_ok_with_dos_line_endings(tmpdir):
|
||||||
filename = tmpdir.join('f')
|
filename = tmpdir.join('f')
|
||||||
filename.write_binary(b'foo\r\nbar\r\nbaz\r\n')
|
filename.write_binary(b'foo\r\nbar\r\nbaz\r\n')
|
||||||
ret = main((str(filename),))
|
ret = main((filename.strpath,))
|
||||||
assert filename.read_binary() == b'foo\r\nbar\r\nbaz\r\n'
|
assert filename.read_binary() == b'foo\r\nbar\r\nbaz\r\n'
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
|
|
||||||
|
|
@ -45,7 +46,7 @@ def test_fixes_markdown_files(tmpdir, ext):
|
||||||
'\t\n' # trailing tabs are stripped anyway
|
'\t\n' # trailing tabs are stripped anyway
|
||||||
'\n ', # whitespace at the end of the file is removed
|
'\n ', # whitespace at the end of the file is removed
|
||||||
)
|
)
|
||||||
ret = main((str(path), f'--markdown-linebreak-ext={ext}'))
|
ret = main((path.strpath, '--markdown-linebreak-ext={}'.format(ext)))
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
assert path.read() == (
|
assert path.read() == (
|
||||||
'foo \n'
|
'foo \n'
|
||||||
|
|
@ -65,7 +66,7 @@ def test_markdown_linebreak_ext_badopt(arg):
|
||||||
|
|
||||||
def test_prints_warning_with_no_markdown_ext(capsys, tmpdir):
|
def test_prints_warning_with_no_markdown_ext(capsys, tmpdir):
|
||||||
f = tmpdir.join('f').ensure()
|
f = tmpdir.join('f').ensure()
|
||||||
assert main((str(f), '--no-markdown-linebreak-ext')) == 0
|
assert main((f.strpath, '--no-markdown-linebreak-ext')) == 0
|
||||||
out, _ = capsys.readouterr()
|
out, _ = capsys.readouterr()
|
||||||
assert out == '--no-markdown-linebreak-ext now does nothing!\n'
|
assert out == '--no-markdown-linebreak-ext now does nothing!\n'
|
||||||
|
|
||||||
|
|
@ -74,7 +75,7 @@ def test_preserve_non_utf8_file(tmpdir):
|
||||||
non_utf8_bytes_content = b'<a>\xe9 \n</a>\n'
|
non_utf8_bytes_content = b'<a>\xe9 \n</a>\n'
|
||||||
path = tmpdir.join('file.txt')
|
path = tmpdir.join('file.txt')
|
||||||
path.write_binary(non_utf8_bytes_content)
|
path.write_binary(non_utf8_bytes_content)
|
||||||
ret = main([str(path)])
|
ret = main([path.strpath])
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
assert path.size() == (len(non_utf8_bytes_content) - 1)
|
assert path.size() == (len(non_utf8_bytes_content) - 1)
|
||||||
|
|
||||||
|
|
@ -83,7 +84,7 @@ def test_custom_charset_change(tmpdir):
|
||||||
# strip spaces only, no tabs
|
# strip spaces only, no tabs
|
||||||
path = tmpdir.join('file.txt')
|
path = tmpdir.join('file.txt')
|
||||||
path.write('\ta \t \n')
|
path.write('\ta \t \n')
|
||||||
ret = main([str(path), '--chars', ' '])
|
ret = main([path.strpath, '--chars', ' '])
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
assert path.read() == '\ta \t\n'
|
assert path.read() == '\ta \t\n'
|
||||||
|
|
||||||
|
|
@ -91,13 +92,13 @@ def test_custom_charset_change(tmpdir):
|
||||||
def test_custom_charset_no_change(tmpdir):
|
def test_custom_charset_no_change(tmpdir):
|
||||||
path = tmpdir.join('file.txt')
|
path = tmpdir.join('file.txt')
|
||||||
path.write('\ta \t\n')
|
path.write('\ta \t\n')
|
||||||
ret = main([str(path), '--chars', ' '])
|
ret = main([path.strpath, '--chars', ' '])
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
|
|
||||||
|
|
||||||
def test_markdown_with_custom_charset(tmpdir):
|
def test_markdown_with_custom_charset(tmpdir):
|
||||||
path = tmpdir.join('file.md')
|
path = tmpdir.join('file.md')
|
||||||
path.write('\ta \t \n')
|
path.write('\ta \t \n')
|
||||||
ret = main([str(path), '--chars', ' ', '--markdown-linebreak-ext', '*'])
|
ret = main([path.strpath, '--chars', ' ', '--markdown-linebreak-ext', '*'])
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
assert path.read() == '\ta \t \n'
|
assert path.read() == '\ta \t \n'
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from __future__ import annotations
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pre_commit_hooks.util import CalledProcessError
|
from pre_commit_hooks.util import CalledProcessError
|
||||||
from pre_commit_hooks.util import cmd_output
|
from pre_commit_hooks.util import cmd_output
|
||||||
from pre_commit_hooks.util import zsplit
|
|
||||||
|
|
||||||
|
|
||||||
def test_raises_on_error():
|
def test_raises_on_error():
|
||||||
|
|
@ -15,13 +15,3 @@ def test_raises_on_error():
|
||||||
def test_output():
|
def test_output():
|
||||||
ret = cmd_output('sh', '-c', 'echo hi')
|
ret = cmd_output('sh', '-c', 'echo hi')
|
||||||
assert ret == 'hi\n'
|
assert ret == 'hi\n'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('out', ('\0f1\0f2\0', '\0f1\0f2', 'f1\0f2\0'))
|
|
||||||
def test_check_zsplits_str_correctly(out):
|
|
||||||
assert zsplit(out) == ['f1', 'f2']
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('out', ('\0\0', '\0', ''))
|
|
||||||
def test_check_zsplit_returns_empty(out):
|
|
||||||
assert zsplit(out) == []
|
|
||||||
|
|
|
||||||
5
tox.ini
5
tox.ini
|
|
@ -1,5 +1,5 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py,pre-commit
|
envlist = py27,py36,py37,pypy,pypy3,pre-commit
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps = -rrequirements-dev.txt
|
deps = -rrequirements-dev.txt
|
||||||
|
|
@ -11,7 +11,8 @@ setenv =
|
||||||
commands =
|
commands =
|
||||||
coverage erase
|
coverage erase
|
||||||
coverage run -m pytest {posargs:tests}
|
coverage run -m pytest {posargs:tests}
|
||||||
coverage report
|
coverage report --fail-under 100
|
||||||
|
pre-commit install
|
||||||
|
|
||||||
[testenv:pre-commit]
|
[testenv:pre-commit]
|
||||||
skip_install = true
|
skip_install = true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue