mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-03-30 10:16:54 +00:00
Compare commits
No commits in common. "main" and "v4.0.1" have entirely different histories.
83 changed files with 864 additions and 1034 deletions
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
github: asottile
|
||||
open_collective: pre-commit
|
||||
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]
|
||||
.coverage
|
||||
.tox
|
||||
.venv.touch
|
||||
/.mypy_cache
|
||||
/.pytest_cache
|
||||
/venv*
|
||||
coverage-html
|
||||
dist
|
||||
|
|
|
|||
|
|
@ -1,41 +1,46 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
rev: v4.0.1
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-docstring-first
|
||||
- id: check-json
|
||||
- id: check-added-large-files
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
- id: double-quote-string-fixer
|
||||
- id: name-tests-test
|
||||
- id: double-quote-string-fixer
|
||||
- id: requirements-txt-fixer
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v3.2.0
|
||||
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
|
||||
rev: 3.9.2
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies: [flake8-typing-imports==1.7.0]
|
||||
- repo: https://github.com/pre-commit/mirrors-autopep8
|
||||
rev: v1.5.7
|
||||
hooks:
|
||||
- id: autopep8
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: [--py3-plus]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.15.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py36-plus]
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
args: [--py36-plus]
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v1.17.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.19.1
|
||||
rev: v0.812
|
||||
hooks:
|
||||
- id: mypy
|
||||
|
|
|
|||
|
|
@ -1,212 +1,193 @@
|
|||
- id: check-added-large-files
|
||||
name: check for added large files
|
||||
description: prevents giant files from being committed.
|
||||
name: Check for added large files
|
||||
description: Prevent giant files from being committed
|
||||
entry: check-added-large-files
|
||||
language: python
|
||||
stages: [pre-commit, pre-push, manual]
|
||||
minimum_pre_commit_version: 3.2.0
|
||||
- id: check-ast
|
||||
name: check python ast
|
||||
description: simply checks whether the files parse as valid python.
|
||||
name: Check python ast
|
||||
description: Simply check whether the files parse as valid python.
|
||||
entry: check-ast
|
||||
language: python
|
||||
types: [python]
|
||||
- id: check-byte-order-marker
|
||||
name: check-byte-order-marker (removed)
|
||||
description: (removed) use fix-byte-order-marker instead.
|
||||
entry: pre-commit-hooks-removed check-byte-order-marker fix-byte-order-marker https://github.com/pre-commit/pre-commit-hooks
|
||||
name: 'check BOM - deprecated: use fix-byte-order-marker'
|
||||
description: forbid files which have a UTF-8 byte-order marker
|
||||
entry: check-byte-order-marker
|
||||
language: python
|
||||
types: [text]
|
||||
- id: check-builtin-literals
|
||||
name: check builtin type constructor use
|
||||
description: requires literal syntax when initializing empty or zero python builtin types.
|
||||
name: Check builtin type constructor use
|
||||
description: Require literal syntax when initializing empty or zero Python builtin types.
|
||||
entry: check-builtin-literals
|
||||
language: python
|
||||
types: [python]
|
||||
- id: check-case-conflict
|
||||
name: check for case conflicts
|
||||
description: checks for files that would conflict in case-insensitive filesystems.
|
||||
name: Check for case conflicts
|
||||
description: Check for files that would conflict in case-insensitive filesystems
|
||||
entry: check-case-conflict
|
||||
language: python
|
||||
- id: check-docstring-first
|
||||
name: check docstring is first (deprecated)
|
||||
description: checks a common error of defining a docstring after code.
|
||||
name: Check docstring is first
|
||||
description: Checks a common error of defining a docstring after code.
|
||||
entry: check-docstring-first
|
||||
language: python
|
||||
types: [python]
|
||||
- id: check-executables-have-shebangs
|
||||
name: check that executables have shebangs
|
||||
description: ensures that (non-binary) executables have a shebang.
|
||||
name: Check that executables have shebangs
|
||||
description: Ensures that (non-binary) executables have a shebang.
|
||||
entry: check-executables-have-shebangs
|
||||
language: python
|
||||
types: [text, executable]
|
||||
stages: [pre-commit, pre-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]$)'
|
||||
stages: [commit, push, manual]
|
||||
- id: check-json
|
||||
name: check json
|
||||
description: checks json files for parseable syntax.
|
||||
name: Check JSON
|
||||
description: This hook checks json files for parseable syntax.
|
||||
entry: check-json
|
||||
language: python
|
||||
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.
|
||||
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
|
||||
stages: [commit, push, manual]
|
||||
- id: pretty-format-json
|
||||
name: pretty format json
|
||||
description: sets a standard for formatting json files.
|
||||
name: Pretty format JSON
|
||||
description: This hook sets a standard for formatting JSON files.
|
||||
entry: pretty-format-json
|
||||
language: python
|
||||
types: [json]
|
||||
- id: check-merge-conflict
|
||||
name: check for merge conflicts
|
||||
description: checks for files that contain merge conflict strings.
|
||||
name: Check for merge conflicts
|
||||
description: Check for files that contain merge conflict strings.
|
||||
entry: check-merge-conflict
|
||||
language: python
|
||||
types: [text]
|
||||
- id: check-symlinks
|
||||
name: check for broken symlinks
|
||||
description: checks for symlinks which do not point to anything.
|
||||
name: Check for broken symlinks
|
||||
description: Checks for symlinks which do not point to anything.
|
||||
entry: check-symlinks
|
||||
language: python
|
||||
types: [symlink]
|
||||
- id: check-toml
|
||||
name: check toml
|
||||
description: checks toml files for parseable syntax.
|
||||
name: Check Toml
|
||||
description: This hook checks toml files for parseable syntax.
|
||||
entry: check-toml
|
||||
language: python
|
||||
types: [toml]
|
||||
- id: check-vcs-permalinks
|
||||
name: check vcs permalinks
|
||||
description: ensures that links to vcs websites are permalinks.
|
||||
name: Check vcs permalinks
|
||||
description: Ensures that links to vcs websites are permalinks.
|
||||
entry: check-vcs-permalinks
|
||||
language: python
|
||||
types: [text]
|
||||
- id: check-xml
|
||||
name: check xml
|
||||
description: checks xml files for parseable syntax.
|
||||
name: Check Xml
|
||||
description: This hook checks xml files for parseable syntax.
|
||||
entry: check-xml
|
||||
language: python
|
||||
types: [xml]
|
||||
- id: check-yaml
|
||||
name: check yaml
|
||||
description: checks yaml files for parseable syntax.
|
||||
name: Check Yaml
|
||||
description: This hook checks yaml files for parseable syntax.
|
||||
entry: check-yaml
|
||||
language: python
|
||||
types: [yaml]
|
||||
- id: debug-statements
|
||||
name: debug statements (python)
|
||||
description: checks for debugger imports and py37+ `breakpoint()` calls in python source.
|
||||
name: Debug Statements (Python)
|
||||
description: Check for debugger imports and py37+ `breakpoint()` calls in python source.
|
||||
entry: debug-statement-hook
|
||||
language: 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.
|
||||
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
|
||||
name: detect aws credentials
|
||||
description: detects *your* aws credentials from the aws cli credentials file.
|
||||
name: Detect AWS Credentials
|
||||
description: Detects *your* aws credentials from the aws cli credentials file
|
||||
entry: detect-aws-credentials
|
||||
language: python
|
||||
types: [text]
|
||||
- id: detect-private-key
|
||||
name: detect private key
|
||||
description: detects the presence of private keys.
|
||||
name: Detect Private Key
|
||||
description: Detects the presence of private keys
|
||||
entry: detect-private-key
|
||||
language: python
|
||||
types: [text]
|
||||
- id: double-quote-string-fixer
|
||||
name: fix double quoted strings
|
||||
description: replaces double quoted strings with single quoted strings.
|
||||
name: Fix double quoted strings
|
||||
description: This hook replaces double quoted strings with single quoted strings
|
||||
entry: double-quote-string-fixer
|
||||
language: python
|
||||
types: [python]
|
||||
- id: end-of-file-fixer
|
||||
name: fix end of files
|
||||
description: ensures that a file is either empty, or ends with one newline.
|
||||
name: Fix End of Files
|
||||
description: Ensures that a file is either empty, or ends with one newline.
|
||||
entry: end-of-file-fixer
|
||||
language: python
|
||||
types: [text]
|
||||
stages: [pre-commit, pre-push, manual]
|
||||
minimum_pre_commit_version: 3.2.0
|
||||
stages: [commit, push, manual]
|
||||
- id: 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.
|
||||
name: File Contents Sorter
|
||||
description: Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input in your .pre-commit-config.yaml file.
|
||||
entry: file-contents-sorter
|
||||
language: python
|
||||
files: '^$'
|
||||
- id: fix-byte-order-marker
|
||||
name: fix utf-8 byte order marker
|
||||
description: removes utf-8 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
|
||||
name: fix python encoding pragma (removed)
|
||||
description: (removed) use pyupgrade instead.
|
||||
entry: pre-commit-hooks-removed fix-encoding-pragma pyupgrade https://github.com/asottile/pyupgrade
|
||||
name: Fix python encoding pragma
|
||||
language: python
|
||||
entry: fix-encoding-pragma
|
||||
description: 'Add # -*- coding: utf-8 -*- to the top of python files'
|
||||
types: [python]
|
||||
- id: forbid-new-submodules
|
||||
name: forbid new submodules
|
||||
description: prevents addition of new git submodules.
|
||||
name: Forbid new submodules
|
||||
language: python
|
||||
entry: forbid-new-submodules
|
||||
types: [directory]
|
||||
- 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]
|
||||
description: Prevent addition of new git submodules
|
||||
- id: mixed-line-ending
|
||||
name: mixed line ending
|
||||
description: replaces or checks mixed line ending.
|
||||
name: Mixed line ending
|
||||
description: Replaces or checks mixed line ending
|
||||
entry: mixed-line-ending
|
||||
language: python
|
||||
types: [text]
|
||||
- id: name-tests-test
|
||||
name: python tests naming
|
||||
description: verifies that test files are named correctly.
|
||||
name: Tests should end in _test.py
|
||||
description: This verifies that test files are named correctly
|
||||
entry: name-tests-test
|
||||
language: python
|
||||
files: (^|/)tests/.+\.py$
|
||||
- id: no-commit-to-branch
|
||||
name: "don't commit to branch"
|
||||
name: "Don't commit to branch"
|
||||
entry: no-commit-to-branch
|
||||
language: python
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
- id: requirements-txt-fixer
|
||||
name: fix requirements.txt
|
||||
description: sorts entries in requirements.txt.
|
||||
name: Fix requirements.txt
|
||||
description: Sorts entries in requirements.txt
|
||||
entry: requirements-txt-fixer
|
||||
language: python
|
||||
files: (requirements|constraints).*\.txt$
|
||||
files: requirements.*\.txt$
|
||||
- id: sort-simple-yaml
|
||||
name: sort simple yaml files
|
||||
description: sorts simple yaml files which consist only of top-level keys, preserving comments and blocks.
|
||||
name: Sort simple YAML files
|
||||
language: python
|
||||
entry: sort-simple-yaml
|
||||
description: Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks.
|
||||
files: '^$'
|
||||
- id: trailing-whitespace
|
||||
name: trim trailing whitespace
|
||||
description: trims trailing whitespace.
|
||||
name: Trim Trailing Whitespace
|
||||
description: This hook trims trailing whitespace.
|
||||
entry: trailing-whitespace-fixer
|
||||
language: python
|
||||
types: [text]
|
||||
stages: [pre-commit, pre-push, manual]
|
||||
minimum_pre_commit_version: 3.2.0
|
||||
stages: [commit, push, manual]
|
||||
|
|
|
|||
166
CHANGELOG.md
166
CHANGELOG.md
|
|
@ -1,169 +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
|
||||
==================
|
||||
|
||||
|
|
|
|||
54
README.md
54
README.md
|
|
@ -1,5 +1,6 @@
|
|||
[](https://github.com/pre-commit/pre-commit-hooks/actions/workflows/main.yml)
|
||||
[](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit-hooks/main)
|
||||
[](https://asottile.visualstudio.com/asottile/_build/latest?definitionId=17&branchName=master)
|
||||
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=17&branchName=master)
|
||||
[](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit-hooks/master)
|
||||
|
||||
pre-commit-hooks
|
||||
================
|
||||
|
|
@ -15,7 +16,7 @@ Add this to your `.pre-commit-config.yaml`
|
|||
|
||||
```yaml
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0 # Use the ref you want to point at
|
||||
rev: v4.0.1 # Use the ref you want to point at
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
# - id: ...
|
||||
|
|
@ -45,18 +46,17 @@ Require literal syntax when initializing empty or zero Python builtin types.
|
|||
#### `check-case-conflict`
|
||||
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`
|
||||
Checks that non-binary executables have a proper shebang.
|
||||
|
||||
#### `check-illegal-windows-names`
|
||||
Check for files that cannot be created on Windows.
|
||||
|
||||
#### `check-json`
|
||||
Attempts to load all json files to verify syntax.
|
||||
|
||||
#### `check-merge-conflict`
|
||||
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.
|
||||
|
|
@ -113,28 +113,21 @@ This hook replaces double quoted strings with single quoted strings.
|
|||
#### `end-of-file-fixer`
|
||||
Makes sure files end in a newline and only a newline.
|
||||
|
||||
#### `file-contents-sorter`
|
||||
Sort the lines in specified files (defaults to alphabetical).
|
||||
You must provide the target [`files`](https://pre-commit.com/#config-files) as input.
|
||||
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
|
||||
|
||||
#### `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`
|
||||
Sort the lines in specified files (defaults to alphabetical).
|
||||
You must provide list of target files as input to it.
|
||||
Note that this hook WILL remove blank lines and does NOT respect any comments.
|
||||
|
||||
#### `forbid-new-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`
|
||||
Replaces or checks mixed line ending.
|
||||
- `--fix={auto,crlf,lf,no}`
|
||||
|
|
@ -144,15 +137,13 @@ Replaces or checks mixed line ending.
|
|||
- `no` - Checks if there is any mixed line ending without modifying any file.
|
||||
|
||||
#### `name-tests-test`
|
||||
verifies that test files are named correctly.
|
||||
- `--pytest` (the default): ensure tests match `.*_test\.py`
|
||||
- `--pytest-test-first`: ensure tests match `test_.*\.py`
|
||||
- `--django` / `--unittest`: ensure tests match `test.*\.py`
|
||||
Assert that files in tests/ end in `_test.py`.
|
||||
- Use `args: ['--django']` to match `test*.py` instead.
|
||||
|
||||
#### `no-commit-to-branch`
|
||||
Protect specific branches from direct checkins.
|
||||
- Use `args: [--branch, staging, --branch, main]` to set the branch.
|
||||
Both `main` and `master` are protected by default if no branch argument is set.
|
||||
- Use `args: [--branch, staging, --branch, master]` to set the branch.
|
||||
Both `master` and `main` are protected by default if no branch argument is set.
|
||||
- `-b` / `--branch` may be specified multiple times to protect multiple
|
||||
branches.
|
||||
- `-p` / `--pattern` can be used to protect branches that match a supplied regex
|
||||
|
|
@ -176,7 +167,7 @@ the following commandline options:
|
|||
- `--top-keys comma,separated,keys` - Keys to keep at the top of mappings.
|
||||
|
||||
#### `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`
|
||||
Sorts simple YAML files which consist only of top-level
|
||||
|
|
@ -203,9 +194,6 @@ Trims trailing whitespace.
|
|||
### Deprecated / replaced hooks
|
||||
|
||||
- `check-byte-order-marker`: instead use fix-byte-order-marker
|
||||
- `fix-encoding-pragma`: instead use [`pyupgrade`](https://github.com/asottile/pyupgrade)
|
||||
- `check-docstring-first`: fundamentally flawed, deprecated without replacement.
|
||||
|
||||
|
||||
### As a standalone package
|
||||
|
||||
|
|
|
|||
23
azure-pipelines.yml
Normal file
23
azure-pipelines.yml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
trigger:
|
||||
branches:
|
||||
include: [master, test-me-*]
|
||||
tags:
|
||||
include: ['*']
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: asottile
|
||||
type: github
|
||||
endpoint: github
|
||||
name: asottile/azure-pipeline-templates
|
||||
ref: refs/tags/v2.1.0
|
||||
|
||||
jobs:
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [py38]
|
||||
os: windows
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [pypy3, py36, py37, py38]
|
||||
os: linux
|
||||
|
|
@ -1,33 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import subprocess
|
||||
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 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)
|
||||
"""Remove files tracked by git-lfs from the set."""
|
||||
if not filenames:
|
||||
return
|
||||
def lfs_files() -> Set[str]:
|
||||
try:
|
||||
# Introduced in git-lfs 2.2.0, first working in 2.2.1
|
||||
lfs_ret = cmd_output('git', 'lfs', 'status', '--json')
|
||||
except CalledProcessError: # pragma: no cover (with git-lfs)
|
||||
lfs_ret = '{"files":{}}'
|
||||
|
||||
check_attr = subprocess.run(
|
||||
('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)
|
||||
return set(json.loads(lfs_ret)['files'])
|
||||
|
||||
|
||||
def find_large_added_files(
|
||||
|
|
@ -39,14 +30,12 @@ def find_large_added_files(
|
|||
# Find all added files that are also in the list of files pre-commit tells
|
||||
# us about
|
||||
retv = 0
|
||||
filenames_filtered = set(filenames)
|
||||
filter_lfs_files(filenames_filtered)
|
||||
|
||||
filenames_filtered = set(filenames) - lfs_files()
|
||||
if not enforce_all:
|
||||
filenames_filtered &= added_files()
|
||||
|
||||
for filename in filenames_filtered:
|
||||
kb = math.ceil(os.stat(filename).st_size / 1024)
|
||||
kb = int(math.ceil(os.stat(filename).st_size / 1024))
|
||||
if kb > maxkb:
|
||||
print(f'{filename} ({kb} KB) exceeds {maxkb} KB.')
|
||||
retv = 1
|
||||
|
|
@ -54,7 +43,7 @@ def find_large_added_files(
|
|||
return retv
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'filenames', nargs='*',
|
||||
|
|
@ -66,7 +55,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
)
|
||||
parser.add_argument(
|
||||
'--maxkb', type=int, default=500,
|
||||
help='Maximum allowable KB for added files',
|
||||
help='Maxmimum allowable KB for added files',
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
|
|
@ -78,4 +67,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import platform
|
||||
import sys
|
||||
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: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -30,4 +29,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
from collections.abc import Sequence
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
|
||||
|
||||
BUILTIN_TYPES = {
|
||||
|
|
@ -26,39 +27,38 @@ class Call(NamedTuple):
|
|||
class Visitor(ast.NodeVisitor):
|
||||
def __init__(
|
||||
self,
|
||||
ignore: set[str],
|
||||
ignore: Optional[Sequence[str]] = None,
|
||||
allow_dict_kwargs: bool = True,
|
||||
) -> None:
|
||||
self.builtin_type_calls: list[Call] = []
|
||||
self.builtin_type_calls: List[Call] = []
|
||||
self.ignore = set(ignore) if ignore else set()
|
||||
self.allow_dict_kwargs = allow_dict_kwargs
|
||||
self._disallowed = BUILTIN_TYPES.keys() - ignore
|
||||
|
||||
def _check_dict_call(self, node: ast.Call) -> bool:
|
||||
return self.allow_dict_kwargs and bool(node.keywords)
|
||||
|
||||
def visit_Call(self, node: ast.Call) -> None:
|
||||
if (
|
||||
if not isinstance(node.func, ast.Name):
|
||||
# Ignore functions that are object attributes (`foo.bar()`).
|
||||
# Assume that if the user calls `builtins.list()`, they know what
|
||||
# they're doing.
|
||||
isinstance(node.func, ast.Name) and
|
||||
node.func.id in self._disallowed and
|
||||
(node.func.id != 'dict' or not self._check_dict_call(node)) and
|
||||
not node.args
|
||||
):
|
||||
self.builtin_type_calls.append(
|
||||
Call(node.func.id, node.lineno, node.col_offset),
|
||||
)
|
||||
|
||||
self.generic_visit(node)
|
||||
return
|
||||
if node.func.id not in set(BUILTIN_TYPES).difference(self.ignore):
|
||||
return
|
||||
if node.func.id == 'dict' and self._check_dict_call(node):
|
||||
return
|
||||
elif node.args:
|
||||
return
|
||||
self.builtin_type_calls.append(
|
||||
Call(node.func.id, node.lineno, node.col_offset),
|
||||
)
|
||||
|
||||
|
||||
def check_file(
|
||||
filename: str,
|
||||
*,
|
||||
ignore: set[str],
|
||||
ignore: Optional[Sequence[str]] = None,
|
||||
allow_dict_kwargs: bool = True,
|
||||
) -> list[Call]:
|
||||
) -> List[Call]:
|
||||
with open(filename, 'rb') as f:
|
||||
tree = ast.parse(f.read(), filename=filename)
|
||||
visitor = Visitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs)
|
||||
|
|
@ -66,11 +66,11 @@ def check_file(
|
|||
return visitor.builtin_type_calls
|
||||
|
||||
|
||||
def parse_ignore(value: str) -> set[str]:
|
||||
def parse_ignore(value: str) -> Set[str]:
|
||||
return set(value.split(','))
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
parser.add_argument('--ignore', type=parse_ignore, default=set())
|
||||
|
|
@ -103,4 +103,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
23
pre_commit_hooks/check_byte_order_marker.py
Normal file
23
pre_commit_hooks/check_byte_order_marker.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import argparse
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = 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:
|
||||
if f.read(3) == b'\xef\xbb\xbf':
|
||||
retv = 1
|
||||
print(f'{filename}: Has a byte-order marker')
|
||||
|
||||
return retv
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
|
|
@ -1,27 +1,27 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Iterator
|
||||
from collections.abc import Sequence
|
||||
import os.path
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
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 cmd_output
|
||||
|
||||
|
||||
def lower_set(iterable: Iterable[str]) -> set[str]:
|
||||
def lower_set(iterable: Iterable[str]) -> Set[str]:
|
||||
return {x.lower() for x in iterable}
|
||||
|
||||
|
||||
def parents(file: str) -> Iterator[str]:
|
||||
path_parts = file.split('/')
|
||||
path_parts.pop()
|
||||
while path_parts:
|
||||
yield '/'.join(path_parts)
|
||||
path_parts.pop()
|
||||
file = os.path.dirname(file)
|
||||
while file:
|
||||
yield file
|
||||
file = os.path.dirname(file)
|
||||
|
||||
|
||||
def directories_for(files: set[str]) -> set[str]:
|
||||
def directories_for(files: Set[str]) -> Set[str]:
|
||||
return {parent for file in files for parent in parents(file)}
|
||||
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ def find_conflicting_filenames(filenames: Sequence[str]) -> int:
|
|||
return retv
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'filenames', nargs='*',
|
||||
|
|
@ -69,4 +69,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import tokenize
|
||||
from collections.abc import Sequence
|
||||
from tokenize import tokenize as tokenize_tokenize
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
NON_CODE_TOKENS = frozenset((
|
||||
tokenize.COMMENT, tokenize.ENDMARKER, tokenize.NEWLINE, tokenize.NL,
|
||||
|
|
@ -28,13 +27,13 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
|
|||
if tok_type == tokenize.STRING and scol == 0:
|
||||
if found_docstring_line is not None:
|
||||
print(
|
||||
f'{filename}:{sline}: Multiple module docstrings '
|
||||
f'{filename}:{sline} Multiple module docstrings '
|
||||
f'(first docstring on line {found_docstring_line}).',
|
||||
)
|
||||
return 1
|
||||
elif found_code_line is not None:
|
||||
print(
|
||||
f'{filename}:{sline}: Module docstring appears after code '
|
||||
f'{filename}:{sline} Module docstring appears after code '
|
||||
f'(code seen on line {found_code_line}).',
|
||||
)
|
||||
return 1
|
||||
|
|
@ -46,7 +45,7 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
|
|||
return 0
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
"""Check that executable text files have a shebang."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shlex
|
||||
import sys
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from pre_commit_hooks.util import zsplit
|
||||
|
|
@ -14,11 +15,8 @@ from pre_commit_hooks.util import zsplit
|
|||
EXECUTABLE_VALUES = frozenset(('1', '3', '5', '7'))
|
||||
|
||||
|
||||
def check_executables(paths: list[str]) -> int:
|
||||
fs_tracks_executable_bit = cmd_output(
|
||||
'git', 'config', 'core.fileMode', retcode=None,
|
||||
).strip()
|
||||
if fs_tracks_executable_bit == 'false': # pragma: win32 cover
|
||||
def check_executables(paths: List[str]) -> int:
|
||||
if sys.platform == 'win32': # pragma: win32 cover
|
||||
return _check_git_filemode(paths)
|
||||
else: # pragma: win32 no cover
|
||||
retv = 0
|
||||
|
|
@ -35,7 +33,7 @@ class GitLsFile(NamedTuple):
|
|||
filename: str
|
||||
|
||||
|
||||
def git_ls_files(paths: Sequence[str]) -> Generator[GitLsFile]:
|
||||
def git_ls_files(paths: Sequence[str]) -> Generator[GitLsFile, None, None]:
|
||||
outs = cmd_output('git', 'ls-files', '-z', '--stage', '--', *paths)
|
||||
for out in zsplit(outs):
|
||||
metadata, filename = out.split('\t')
|
||||
|
|
@ -44,7 +42,7 @@ def git_ls_files(paths: Sequence[str]) -> Generator[GitLsFile]:
|
|||
|
||||
|
||||
def _check_git_filemode(paths: Sequence[str]) -> int:
|
||||
seen: set[str] = set()
|
||||
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):
|
||||
|
|
@ -66,14 +64,12 @@ def _message(path: str) -> None:
|
|||
f'{path}: marked executable but has no (or invalid) shebang!\n'
|
||||
f" If it isn't 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 supposed to be executable, double-check its shebang.',
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -82,4 +78,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
def raise_duplicate_keys(
|
||||
ordered_pairs: list[tuple[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
ordered_pairs: List[Tuple[str, Any]],
|
||||
) -> Dict[str, Any]:
|
||||
d = {}
|
||||
for key, val in ordered_pairs:
|
||||
if key in d:
|
||||
|
|
@ -18,7 +20,7 @@ def raise_duplicate_keys(
|
|||
return d
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -35,4 +37,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,34 +1,29 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os.path
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
CONFLICT_PATTERNS = [
|
||||
b'<<<<<<< ',
|
||||
b'======= ',
|
||||
b'=======\r\n',
|
||||
b'=======\n',
|
||||
b'>>>>>>> ',
|
||||
]
|
||||
|
||||
|
||||
def is_in_merge() -> bool:
|
||||
git_dir = cmd_output('git', 'rev-parse', '--git-dir').rstrip()
|
||||
def is_in_merge() -> int:
|
||||
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_dir, 'rebase-apply')) or
|
||||
os.path.exists(os.path.join(git_dir, 'rebase-merge'))
|
||||
os.path.exists(os.path.join('.git', 'MERGE_HEAD')) or
|
||||
os.path.exists(os.path.join('.git', 'rebase-apply')) or
|
||||
os.path.exists(os.path.join('.git', 'rebase-merge'))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
parser.add_argument('--assume-in-merge', action='store_true')
|
||||
|
|
@ -40,12 +35,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
retcode = 0
|
||||
for filename in args.filenames:
|
||||
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:
|
||||
if line.startswith(pattern):
|
||||
print(
|
||||
f'{filename}:{i}: Merge conflict string '
|
||||
f'{pattern.strip().decode()!r} found',
|
||||
f'Merge conflict string "{pattern.decode()}" '
|
||||
f'found in {filename}:{i + 1}',
|
||||
)
|
||||
retcode = 1
|
||||
|
||||
|
|
@ -53,4 +48,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
"""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 typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
|
||||
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:
|
||||
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.
|
||||
|
|
@ -19,7 +20,7 @@ def check_shebangs(paths: list[str]) -> int:
|
|||
|
||||
|
||||
def _check_git_filemode(paths: Sequence[str]) -> int:
|
||||
seen: set[str] = set()
|
||||
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):
|
||||
|
|
@ -34,15 +35,13 @@ def _message(path: str) -> None:
|
|||
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' If it not supposed to be executable, double-check its shebang '
|
||||
f'is wanted.\n',
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -51,4 +50,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
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: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser(description='Checks for broken symlinks.')
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -24,4 +23,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
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 tomllib
|
||||
else: # pragma: <3.11 cover
|
||||
import tomli as tomllib
|
||||
import toml
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -18,13 +13,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
retval = 0
|
||||
for filename in args.filenames:
|
||||
try:
|
||||
with open(filename, mode='rb') as fp:
|
||||
tomllib.load(fp)
|
||||
except tomllib.TOMLDecodeError as exc:
|
||||
toml.load(filename)
|
||||
except toml.TomlDecodeError as exc:
|
||||
print(f'{filename}: {exc}')
|
||||
retval = 1
|
||||
return retval
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from re import Pattern
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def _get_pattern(domain: str) -> Pattern[bytes]:
|
||||
|
|
@ -15,7 +15,7 @@ def _get_pattern(domain: str) -> Pattern[bytes]:
|
|||
return re.compile(regex.encode())
|
||||
|
||||
|
||||
def _check_filename(filename: str, patterns: list[Pattern[bytes]]) -> int:
|
||||
def _check_filename(filename: str, patterns: List[Pattern[bytes]]) -> int:
|
||||
retv = 0
|
||||
with open(filename, 'rb') as f:
|
||||
for i, line in enumerate(f, 1):
|
||||
|
|
@ -28,7 +28,7 @@ def _check_filename(filename: str, patterns: list[Pattern[bytes]]) -> int:
|
|||
return retv
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
parser.add_argument(
|
||||
|
|
@ -57,4 +57,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
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: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='XML filenames to check.')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -23,4 +22,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import Generator
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
import ruamel.yaml
|
||||
|
||||
yaml = ruamel.yaml.YAML(typ='safe')
|
||||
|
||||
|
||||
def _exhaust(gen: Generator[str]) -> None:
|
||||
def _exhaust(gen: Generator[str, None, None]) -> None:
|
||||
for _ in gen:
|
||||
pass
|
||||
|
||||
|
|
@ -37,7 +36,7 @@ LOAD_FNS = {
|
|||
}
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-m', '--multi', '--allow-multiple-documents', action='store_true',
|
||||
|
|
@ -46,7 +45,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
'--unsafe', action='store_true',
|
||||
help=(
|
||||
'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 '
|
||||
'all guarantees of portability to other yaml implementations. '
|
||||
'Implies --allow-multiple-documents'
|
||||
|
|
@ -69,4 +68,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
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 = {
|
||||
'bpdb',
|
||||
'ipdb',
|
||||
'pdb',
|
||||
'pdbr',
|
||||
'pudb',
|
||||
'pydevd_pycharm',
|
||||
'q',
|
||||
|
|
@ -30,7 +28,7 @@ class Debug(NamedTuple):
|
|||
|
||||
class DebugStatementParser(ast.NodeVisitor):
|
||||
def __init__(self) -> None:
|
||||
self.breakpoints: list[Debug] = []
|
||||
self.breakpoints: List[Debug] = []
|
||||
|
||||
def visit_Import(self, node: ast.Import) -> None:
|
||||
for name in node.names:
|
||||
|
|
@ -66,12 +64,12 @@ def check_file(filename: str) -> int:
|
|||
visitor.visit(ast_obj)
|
||||
|
||||
for bp in visitor.breakpoints:
|
||||
print(f'{filename}:{bp.line}:{bp.col}: {bp.name} {bp.reason}')
|
||||
print(f'{filename}:{bp.line}:{bp.col} - {bp.name} {bp.reason}')
|
||||
|
||||
return int(bool(visitor.breakpoints))
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to run')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -83,4 +81,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
20
pre_commit_hooks/destroyed_symlinks.py
Normal file → Executable file
20
pre_commit_hooks/destroyed_symlinks.py
Normal file → Executable file
|
|
@ -1,9 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shlex
|
||||
import subprocess
|
||||
from collections.abc import Sequence
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from pre_commit_hooks.util import zsplit
|
||||
|
|
@ -13,8 +13,8 @@ PERMS_LINK = '120000'
|
|||
PERMS_NONEXIST = '000000'
|
||||
|
||||
|
||||
def find_destroyed_symlinks(files: Sequence[str]) -> list[str]:
|
||||
destroyed_links: list[str] = []
|
||||
def find_destroyed_symlinks(files: Sequence[str]) -> List[str]:
|
||||
destroyed_links: List[str] = []
|
||||
if not files:
|
||||
return destroyed_links
|
||||
for line in zsplit(
|
||||
|
|
@ -66,7 +66,7 @@ def find_destroyed_symlinks(files: Sequence[str]) -> list[str]:
|
|||
return destroyed_links
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -76,7 +76,11 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
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(
|
||||
'\tgit reset HEAD -- {}'.format(
|
||||
' '.join(shlex.quote(link) for link in destroyed_links),
|
||||
),
|
||||
)
|
||||
print(
|
||||
'And retry commit. As a long term solution '
|
||||
'you may try to explicitly tell git that your '
|
||||
|
|
@ -89,4 +93,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
|
||||
|
||||
class BadFile(NamedTuple):
|
||||
|
|
@ -12,7 +13,7 @@ class BadFile(NamedTuple):
|
|||
key: str
|
||||
|
||||
|
||||
def get_aws_cred_files_from_env() -> set[str]:
|
||||
def get_aws_cred_files_from_env() -> Set[str]:
|
||||
"""Extract credential file paths from environment variables."""
|
||||
return {
|
||||
os.environ[env_var]
|
||||
|
|
@ -24,7 +25,7 @@ def get_aws_cred_files_from_env() -> set[str]:
|
|||
}
|
||||
|
||||
|
||||
def get_aws_secrets_from_env() -> set[str]:
|
||||
def get_aws_secrets_from_env() -> Set[str]:
|
||||
"""Extract AWS secrets from environment variables."""
|
||||
keys = set()
|
||||
for env_var in (
|
||||
|
|
@ -35,7 +36,7 @@ def get_aws_secrets_from_env() -> set[str]:
|
|||
return keys
|
||||
|
||||
|
||||
def get_aws_secrets_from_file(credentials_file: str) -> set[str]:
|
||||
def get_aws_secrets_from_file(credentials_file: str) -> Set[str]:
|
||||
"""Extract AWS secrets from configuration files.
|
||||
|
||||
Read an ini-style configuration file and return a set with all found AWS
|
||||
|
|
@ -68,8 +69,8 @@ def get_aws_secrets_from_file(credentials_file: str) -> set[str]:
|
|||
|
||||
def check_file_for_aws_keys(
|
||||
filenames: Sequence[str],
|
||||
keys: set[bytes],
|
||||
) -> list[BadFile]:
|
||||
keys: Set[bytes],
|
||||
) -> List[BadFile]:
|
||||
"""Check if files contain AWS secrets.
|
||||
|
||||
Return a list of all files containing AWS secrets and keys found, with all
|
||||
|
|
@ -89,7 +90,7 @@ def check_file_for_aws_keys(
|
|||
return bad_files
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='+', help='Filenames to run')
|
||||
parser.add_argument(
|
||||
|
|
@ -118,7 +119,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
# of files to to gather AWS secrets from.
|
||||
credential_files |= get_aws_cred_files_from_env()
|
||||
|
||||
keys: set[str] = set()
|
||||
keys: Set[str] = set()
|
||||
for credential_file in credential_files:
|
||||
keys |= get_aws_secrets_from_file(credential_file)
|
||||
|
||||
|
|
@ -148,4 +149,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
BLACKLIST = [
|
||||
b'BEGIN RSA PRIVATE KEY',
|
||||
|
|
@ -12,12 +11,10 @@ BLACKLIST = [
|
|||
b'PuTTY-User-Key-File-2',
|
||||
b'BEGIN SSH2 ENCRYPTED PRIVATE KEY',
|
||||
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: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -39,4 +36,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
from typing import IO
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def fix_file(file_obj: IO[bytes]) -> int:
|
||||
|
|
@ -49,7 +48,7 @@ def fix_file(file_obj: IO[bytes]) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -68,4 +67,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -9,14 +9,13 @@ per line. Various users are adding/removing lines from this file; using
|
|||
this hook on that file should reduce the instances of git merge
|
||||
conflicts and keep the file nicely ordered.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
PASS = 0
|
||||
FAIL = 1
|
||||
|
|
@ -24,7 +23,7 @@ FAIL = 1
|
|||
|
||||
def sort_file_contents(
|
||||
f: IO[bytes],
|
||||
key: Callable[[bytes], Any] | None,
|
||||
key: Optional[Callable[[bytes], Any]],
|
||||
*,
|
||||
unique: bool = False,
|
||||
) -> int:
|
||||
|
|
@ -37,10 +36,7 @@ def sort_file_contents(
|
|||
after = sorted(lines, key=key)
|
||||
|
||||
before_string = b''.join(before)
|
||||
after_string = b'\n'.join(after)
|
||||
|
||||
if after_string:
|
||||
after_string += b'\n'
|
||||
after_string = b'\n'.join(after) + b'\n'
|
||||
|
||||
if before_string == after_string:
|
||||
return PASS
|
||||
|
|
@ -51,24 +47,21 @@ def sort_file_contents(
|
|||
return FAIL
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='+', help='Files to sort')
|
||||
|
||||
mutex = parser.add_mutually_exclusive_group(required=False)
|
||||
mutex.add_argument(
|
||||
parser.add_argument(
|
||||
'--ignore-case',
|
||||
action='store_const',
|
||||
const=bytes.lower,
|
||||
default=None,
|
||||
help='fold lower case to upper case characters',
|
||||
)
|
||||
mutex.add_argument(
|
||||
parser.add_argument(
|
||||
'--unique',
|
||||
action='store_true',
|
||||
help='ensure each line is unique',
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
retv = PASS
|
||||
|
|
@ -88,4 +81,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -28,4 +27,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
148
pre_commit_hooks/fix_encoding_pragma.py
Normal file
148
pre_commit_hooks/fix_encoding_pragma.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import argparse
|
||||
from typing import IO
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
DEFAULT_PRAGMA = b'# -*- coding: utf-8 -*-'
|
||||
|
||||
|
||||
def has_coding(line: 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(NamedTuple):
|
||||
shebang: bytes
|
||||
rest: bytes
|
||||
# True: has exactly the coding pragma expected
|
||||
# False: missing coding pragma entirely
|
||||
# None: has a coding pragma, but it does not match
|
||||
pragma_status: Optional[bool]
|
||||
ending: bytes
|
||||
|
||||
@property
|
||||
def has_any_pragma(self) -> bool:
|
||||
return self.pragma_status is not False
|
||||
|
||||
def is_expected_pragma(self, remove: bool) -> bool:
|
||||
expected_pragma_status = not remove
|
||||
return self.pragma_status is expected_pragma_status
|
||||
|
||||
|
||||
def _get_expected_contents(
|
||||
first_line: bytes,
|
||||
second_line: bytes,
|
||||
rest: bytes,
|
||||
expected_pragma: 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: Optional[bool] = True
|
||||
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: IO[bytes],
|
||||
remove: bool = False,
|
||||
expected_pragma: bytes = DEFAULT_PRAGMA,
|
||||
) -> 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: str) -> bytes:
|
||||
return pragma.encode().rstrip()
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = None) -> 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=(
|
||||
f'The encoding pragma to use. '
|
||||
f'Default: {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,30 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
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'
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
# `argv` is ignored, pre-commit will send us a list of files that we
|
||||
# don't care about
|
||||
added_diff = cmd_output(
|
||||
'git', 'diff', '--diff-filter=A', '--raw', diff_arg, '--',
|
||||
*args.filenames,
|
||||
'git', 'diff', '--staged', '--diff-filter=A', '--raw',
|
||||
)
|
||||
retv = 0
|
||||
for line in added_diff.splitlines():
|
||||
|
|
@ -45,4 +29,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
from collections.abc import Sequence
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
CRLF = b'\r\n'
|
||||
|
|
@ -25,7 +25,7 @@ def fix_filename(filename: str, fix: str) -> int:
|
|||
with open(filename, 'rb') as f:
|
||||
contents = f.read()
|
||||
|
||||
counts: dict[bytes, int] = collections.defaultdict(int)
|
||||
counts: Dict[bytes, int] = collections.defaultdict(int)
|
||||
|
||||
for line in contents.splitlines(True):
|
||||
for ending in ALL_ENDINGS:
|
||||
|
|
@ -62,7 +62,7 @@ def fix_filename(filename: str, fix: str) -> int:
|
|||
return other_endings
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-f', '--fix',
|
||||
|
|
@ -85,4 +85,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
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 cmd_output
|
||||
|
|
@ -24,7 +23,7 @@ def is_on_branch(
|
|||
)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-b', '--branch', action='append',
|
||||
|
|
@ -45,4 +44,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from collections.abc import Mapping
|
||||
from collections.abc import Sequence
|
||||
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
|
||||
|
||||
|
||||
def _get_pretty_format(
|
||||
|
|
@ -15,7 +17,7 @@ def _get_pretty_format(
|
|||
sort_keys: bool = True,
|
||||
top_keys: Sequence[str] = (),
|
||||
) -> str:
|
||||
def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[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 = sorted(before, key=lambda x: top_keys.index(x[0]))
|
||||
after = [pair for pair in pairs if pair[0] not in top_keys]
|
||||
|
|
@ -36,7 +38,7 @@ def _autofix(filename: str, new_contents: str) -> None:
|
|||
f.write(new_contents)
|
||||
|
||||
|
||||
def parse_num_to_int(s: str) -> int | str:
|
||||
def parse_num_to_int(s: str) -> Union[int, str]:
|
||||
"""Convert string numbers to int, leaving strings as is."""
|
||||
try:
|
||||
return int(s)
|
||||
|
|
@ -44,7 +46,7 @@ def parse_num_to_int(s: str) -> int | str:
|
|||
return s
|
||||
|
||||
|
||||
def parse_topkeys(s: str) -> list[str]:
|
||||
def parse_topkeys(s: str) -> List[str]:
|
||||
return s.split(',')
|
||||
|
||||
|
||||
|
|
@ -55,7 +57,7 @@ def get_diff(source: str, target: str, file: str) -> str:
|
|||
return ''.join(diff)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'--autofix',
|
||||
|
|
@ -115,23 +117,19 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
f'Input File {json_file} is not a valid JSON, consider using '
|
||||
f'check-json',
|
||||
)
|
||||
status = 1
|
||||
else:
|
||||
if contents != pretty_contents:
|
||||
if args.autofix:
|
||||
_autofix(json_file, pretty_contents)
|
||||
else:
|
||||
diff_output = get_diff(
|
||||
contents,
|
||||
pretty_contents,
|
||||
json_file,
|
||||
)
|
||||
sys.stdout.buffer.write(diff_output.encode())
|
||||
return 1
|
||||
|
||||
status = 1
|
||||
if contents != pretty_contents:
|
||||
if args.autofix:
|
||||
_autofix(json_file, pretty_contents)
|
||||
else:
|
||||
diff_output = get_diff(contents, pretty_contents, json_file)
|
||||
sys.stdout.buffer.write(diff_output.encode())
|
||||
|
||||
status = 1
|
||||
|
||||
return status
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
argv = argv if argv is not None else sys.argv[1:]
|
||||
hookid, new_hookid, url = argv[:3]
|
||||
raise SystemExit(
|
||||
|
|
@ -13,4 +12,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
from typing import IO
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
PASS = 0
|
||||
|
|
@ -15,8 +15,8 @@ class Requirement:
|
|||
UNTIL_SEP = re.compile(rb'[^;\s]+')
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.value: bytes | None = None
|
||||
self.comments: list[bytes] = []
|
||||
self.value: Optional[bytes] = None
|
||||
self.comments: List[bytes] = []
|
||||
|
||||
@property
|
||||
def name(self) -> bytes:
|
||||
|
|
@ -36,7 +36,7 @@ class Requirement:
|
|||
|
||||
return name[:m.start()]
|
||||
|
||||
def __lt__(self, requirement: Requirement) -> bool:
|
||||
def __lt__(self, requirement: 'Requirement') -> bool:
|
||||
# \n means top of file comment, so always return True,
|
||||
# otherwise just do a string comparison with value.
|
||||
assert self.value is not None, self.value
|
||||
|
|
@ -45,11 +45,6 @@ class Requirement:
|
|||
elif requirement.value == b'\n':
|
||||
return False
|
||||
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
|
||||
|
||||
def is_complete(self) -> bool:
|
||||
|
|
@ -66,9 +61,9 @@ class Requirement:
|
|||
|
||||
|
||||
def fix_requirements(f: IO[bytes]) -> int:
|
||||
requirements: list[Requirement] = []
|
||||
requirements: List[Requirement] = []
|
||||
before = list(f)
|
||||
after: list[bytes] = []
|
||||
after: List[bytes] = []
|
||||
|
||||
before_string = b''.join(before)
|
||||
|
||||
|
|
@ -115,20 +110,13 @@ def fix_requirements(f: IO[bytes]) -> int:
|
|||
# which is automatically added by broken pip package under Debian
|
||||
requirements = [
|
||||
req for req in requirements
|
||||
if req.value not in [
|
||||
b'pkg-resources==0.0.0\n',
|
||||
b'pkg_resources==0.0.0\n',
|
||||
]
|
||||
if req.value != b'pkg-resources==0.0.0\n'
|
||||
]
|
||||
|
||||
# sort the requirements and remove duplicates
|
||||
prev = None
|
||||
for requirement in sorted(requirements):
|
||||
after.extend(requirement.comments)
|
||||
assert requirement.value, requirement.value
|
||||
if prev is None or requirement.value != prev.value:
|
||||
after.append(requirement.value)
|
||||
prev = requirement
|
||||
after.append(requirement.value)
|
||||
after.extend(rest)
|
||||
|
||||
after_string = b''.join(after)
|
||||
|
|
@ -142,7 +130,7 @@ def fix_requirements(f: IO[bytes]) -> int:
|
|||
return FAIL
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -162,4 +150,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
19
pre_commit_hooks/sort_simple_yaml.py
Normal file → Executable file
19
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
|
||||
together.
|
||||
|
||||
|
|
@ -17,16 +18,16 @@ 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
|
||||
complicated YAML files.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections.abc import Sequence
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
QUOTES = ["'", '"']
|
||||
|
||||
|
||||
def sort(lines: list[str]) -> list[str]:
|
||||
def sort(lines: List[str]) -> List[str]:
|
||||
"""Sort a YAML file in alphabetical order, keeping blocks together.
|
||||
|
||||
:param lines: array of strings (without newlines)
|
||||
|
|
@ -44,7 +45,7 @@ def sort(lines: list[str]) -> list[str]:
|
|||
return new_lines
|
||||
|
||||
|
||||
def parse_block(lines: list[str], header: bool = False) -> list[str]:
|
||||
def parse_block(lines: List[str], header: bool = False) -> List[str]:
|
||||
"""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
|
||||
|
|
@ -60,7 +61,7 @@ def parse_block(lines: list[str], header: bool = False) -> list[str]:
|
|||
return block_lines
|
||||
|
||||
|
||||
def parse_blocks(lines: list[str]) -> list[list[str]]:
|
||||
def parse_blocks(lines: List[str]) -> List[List[str]]:
|
||||
"""Parse and return all possible blocks, popping off the start of `lines`.
|
||||
|
||||
:param lines: list of lines
|
||||
|
|
@ -77,7 +78,7 @@ def parse_blocks(lines: list[str]) -> list[list[str]]:
|
|||
return blocks
|
||||
|
||||
|
||||
def first_key(lines: list[str]) -> str:
|
||||
def first_key(lines: List[str]) -> str:
|
||||
"""Returns a string representing the sort key of a block.
|
||||
|
||||
The sort key is the first YAML key we encounter, ignoring comments, and
|
||||
|
|
@ -99,7 +100,7 @@ def first_key(lines: list[str]) -> str:
|
|||
return '' # not actually reached in reality
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -122,4 +123,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,17 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import re
|
||||
import sys
|
||||
import tokenize
|
||||
from collections.abc import Sequence
|
||||
|
||||
if sys.version_info >= (3, 12): # pragma: >=3.12 cover
|
||||
FSTRING_START = tokenize.FSTRING_START
|
||||
FSTRING_END = tokenize.FSTRING_END
|
||||
else: # pragma: <3.12 cover
|
||||
FSTRING_START = FSTRING_END = -1
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
START_QUOTE_RE = re.compile('^[a-zA-Z]*"')
|
||||
|
||||
|
|
@ -31,7 +24,7 @@ def handle_match(token_text: str) -> str:
|
|||
return token_text
|
||||
|
||||
|
||||
def get_line_offsets_by_line_no(src: str) -> list[int]:
|
||||
def get_line_offsets_by_line_no(src: str) -> List[int]:
|
||||
# Padded so we can index with line number
|
||||
offsets = [-1, 0]
|
||||
for line in src.splitlines(True):
|
||||
|
|
@ -47,17 +40,11 @@ def fix_strings(filename: str) -> int:
|
|||
# Basically a mutable string
|
||||
splitcontents = list(contents)
|
||||
|
||||
fstring_depth = 0
|
||||
|
||||
# Iterate in reverse so the offsets are always correct
|
||||
tokens_l = list(tokenize.generate_tokens(io.StringIO(contents).readline))
|
||||
tokens = reversed(tokens_l)
|
||||
for token_type, token_text, (srow, scol), (erow, ecol), _ in tokens:
|
||||
if token_type == FSTRING_START: # pragma: >=3.12 cover
|
||||
fstring_depth += 1
|
||||
elif token_type == FSTRING_END: # pragma: >=3.12 cover
|
||||
fstring_depth -= 1
|
||||
elif fstring_depth == 0 and token_type == tokenize.STRING:
|
||||
if token_type == tokenize.STRING:
|
||||
new_text = handle_match(token_text)
|
||||
splitcontents[
|
||||
line_offsets[srow] + scol:
|
||||
|
|
@ -73,7 +60,7 @@ def fix_strings(filename: str) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -90,4 +77,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,53 +1,33 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os.path
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
mutex = parser.add_mutually_exclusive_group()
|
||||
mutex.add_argument(
|
||||
'--pytest',
|
||||
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',
|
||||
parser.add_argument(
|
||||
'--django', default=False, action='store_true',
|
||||
help='Use Django-style test naming pattern (test*.py)',
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
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:
|
||||
base = os.path.basename(filename)
|
||||
if (
|
||||
not reg.fullmatch(base) and
|
||||
not re.match(test_name_pattern, base) and
|
||||
not base == '__init__.py' and
|
||||
not base == 'conftest.py'
|
||||
):
|
||||
retcode = 1
|
||||
print(f'{filename} does not match pattern "{args.pattern}"')
|
||||
print(f'{filename} does not match pattern "{test_name_pattern}"')
|
||||
|
||||
return retcode
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def _fix_file(
|
||||
filename: str,
|
||||
is_markdown: bool,
|
||||
chars: bytes | None,
|
||||
chars: Optional[bytes],
|
||||
) -> bool:
|
||||
with open(filename, mode='rb') as file_processed:
|
||||
lines = file_processed.readlines()
|
||||
|
|
@ -25,7 +24,7 @@ def _fix_file(
|
|||
def _process_line(
|
||||
line: bytes,
|
||||
is_markdown: bool,
|
||||
chars: bytes | None,
|
||||
chars: Optional[bytes],
|
||||
) -> bytes:
|
||||
if line[-2:] == b'\r\n':
|
||||
eol = b'\r\n'
|
||||
|
|
@ -41,7 +40,7 @@ def _process_line(
|
|||
return line.rstrip(chars) + eol
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'--no-markdown-linebreak-ext',
|
||||
|
|
@ -100,4 +99,4 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
exit(main())
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
|
||||
class CalledProcessError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def added_files() -> set[str]:
|
||||
def added_files() -> Set[str]:
|
||||
cmd = ('git', 'diff', '--staged', '--name-only', '--diff-filter=A')
|
||||
return set(cmd_output(*cmd).splitlines())
|
||||
|
||||
|
||||
def cmd_output(*cmd: str, retcode: int | None = 0, **kwargs: Any) -> str:
|
||||
def cmd_output(*cmd: str, retcode: Optional[int] = 0, **kwargs: Any) -> str:
|
||||
kwargs.setdefault('stdout', subprocess.PIPE)
|
||||
kwargs.setdefault('stderr', subprocess.PIPE)
|
||||
proc = subprocess.Popen(cmd, **kwargs)
|
||||
|
|
@ -24,7 +25,7 @@ def cmd_output(*cmd: str, retcode: int | None = 0, **kwargs: Any) -> str:
|
|||
return stdout
|
||||
|
||||
|
||||
def zsplit(s: str) -> list[str]:
|
||||
def zsplit(s: str) -> List[str]:
|
||||
s = s.strip('\0')
|
||||
if s:
|
||||
return s.split('\0')
|
||||
|
|
|
|||
18
setup.cfg
18
setup.cfg
|
|
@ -1,6 +1,6 @@
|
|||
[metadata]
|
||||
name = pre_commit_hooks
|
||||
version = 6.0.0
|
||||
version = 4.0.1
|
||||
description = Some out-of-the-box hooks for pre-commit.
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
|
|
@ -8,10 +8,15 @@ url = https://github.com/pre-commit/pre-commit-hooks
|
|||
author = Anthony Sottile
|
||||
author_email = asottile@umich.edu
|
||||
license = MIT
|
||||
license_files = LICENSE
|
||||
license_file = LICENSE
|
||||
classifiers =
|
||||
License :: OSI Approved :: MIT License
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: Implementation :: CPython
|
||||
Programming Language :: Python :: Implementation :: PyPy
|
||||
|
||||
|
|
@ -19,8 +24,8 @@ classifiers =
|
|||
packages = find:
|
||||
install_requires =
|
||||
ruamel.yaml>=0.15
|
||||
tomli>=1.1.0;python_version<"3.11"
|
||||
python_requires = >=3.10
|
||||
toml
|
||||
python_requires = >=3.6.1
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
|
|
@ -32,6 +37,7 @@ console_scripts =
|
|||
check-added-large-files = pre_commit_hooks.check_added_large_files:main
|
||||
check-ast = pre_commit_hooks.check_ast: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-docstring-first = pre_commit_hooks.check_docstring_first:main
|
||||
check-executables-have-shebangs = pre_commit_hooks.check_executables_have_shebangs:main
|
||||
|
|
@ -51,6 +57,7 @@ console_scripts =
|
|||
end-of-file-fixer = pre_commit_hooks.end_of_file_fixer: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
|
||||
mixed-line-ending = pre_commit_hooks.mixed_line_ending:main
|
||||
name-tests-test = pre_commit_hooks.tests_should_end_in_test:main
|
||||
|
|
@ -72,8 +79,7 @@ check_untyped_defs = true
|
|||
disallow_any_generics = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_untyped_defs = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
no_implicit_optional = true
|
||||
|
||||
[mypy-testing.*]
|
||||
disallow_untyped_defs = false
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -1,4 +1,2 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from setuptools import setup
|
||||
setup()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import subprocess
|
||||
|
||||
|
||||
TESTING_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
|
|
@ -9,8 +6,3 @@ TESTING_DIR = os.path.abspath(os.path.dirname(__file__))
|
|||
|
||||
def get_resource_path(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)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import distutils.spawn
|
||||
|
||||
import pytest
|
||||
|
||||
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.util import cmd_output
|
||||
from testing.util import git_commit
|
||||
|
||||
|
||||
def test_nothing_added(temp_git_dir):
|
||||
|
|
@ -78,7 +75,7 @@ def test_integration(temp_git_dir):
|
|||
|
||||
|
||||
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(
|
||||
|
|
@ -87,9 +84,10 @@ xfailif_no_gitlfs = pytest.mark.xfail(
|
|||
|
||||
|
||||
@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():
|
||||
cmd_output('git', 'lfs', 'install', '--local')
|
||||
monkeypatch.setenv('HOME', str(temp_git_dir))
|
||||
cmd_output('git', 'lfs', 'install')
|
||||
temp_git_dir.join('f.py').write('a' * 10000)
|
||||
cmd_output('git', 'lfs', 'track', 'f.py')
|
||||
cmd_output('git', 'add', '--', '.')
|
||||
|
|
@ -98,37 +96,27 @@ def test_allows_gitlfs(temp_git_dir): # pragma: no cover
|
|||
|
||||
|
||||
@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():
|
||||
cmd_output('git', 'lfs', 'install', '--local')
|
||||
monkeypatch.setenv('HOME', str(temp_git_dir))
|
||||
cmd_output('git', 'lfs', 'install')
|
||||
cmd_output('git', 'lfs', 'track', 'a.bin', 'b.bin')
|
||||
# First add the file we're going to move
|
||||
temp_git_dir.join('a.bin').write('a' * 10000)
|
||||
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
|
||||
cmd_output('git', 'mv', 'a.bin', 'b.bin')
|
||||
assert main(('--maxkb', '9', 'b.bin')) == 0
|
||||
|
||||
|
||||
@xfailif_no_gitlfs
|
||||
def test_enforce_allows_gitlfs(temp_git_dir): # pragma: no cover
|
||||
def test_enforce_allows_gitlfs(temp_git_dir, monkeypatch): # pragma: no cover
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'lfs', 'install', '--local')
|
||||
monkeypatch.setenv('HOME', str(temp_git_dir))
|
||||
cmd_output('git', 'lfs', 'install')
|
||||
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,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks.check_ast import main
|
||||
from testing.util import get_resource_path
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
|
||||
import pytest
|
||||
|
|
@ -38,6 +36,11 @@ t1 = ()
|
|||
'''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def visitor():
|
||||
return Visitor()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('expression', 'calls'),
|
||||
[
|
||||
|
|
@ -80,8 +83,7 @@ t1 = ()
|
|||
('builtins.tuple()', []),
|
||||
],
|
||||
)
|
||||
def test_non_dict_exprs(expression, calls):
|
||||
visitor = Visitor(ignore=set())
|
||||
def test_non_dict_exprs(visitor, expression, calls):
|
||||
visitor.visit(ast.parse(expression))
|
||||
assert visitor.builtin_type_calls == calls
|
||||
|
||||
|
|
@ -98,8 +100,7 @@ def test_non_dict_exprs(expression, calls):
|
|||
('builtins.dict()', []),
|
||||
],
|
||||
)
|
||||
def test_dict_allow_kwargs_exprs(expression, calls):
|
||||
visitor = Visitor(ignore=set())
|
||||
def test_dict_allow_kwargs_exprs(visitor, expression, calls):
|
||||
visitor.visit(ast.parse(expression))
|
||||
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)]),
|
||||
('builtins.dict()', []),
|
||||
pytest.param('f(dict())', [Call('dict', 1, 2)], id='nested'),
|
||||
],
|
||||
)
|
||||
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))
|
||||
assert visitor.builtin_type_calls == calls
|
||||
|
||||
|
||||
def test_ignore_constructors():
|
||||
visitor = Visitor(
|
||||
ignore={'complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'},
|
||||
ignore=('complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'),
|
||||
)
|
||||
visitor.visit(ast.parse(BUILTIN_CONSTRUCTORS))
|
||||
assert visitor.builtin_type_calls == []
|
||||
|
|
|
|||
13
tests/check_byte_order_marker_test.py
Normal file
13
tests/check_byte_order_marker_test.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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((str(f),)) == 1
|
||||
|
||||
|
||||
def test_success(tmpdir):
|
||||
f = tmpdir.join('f.txt')
|
||||
f.write_text('ohai', encoding='utf-8')
|
||||
assert check_byte_order_marker.main((str(f),)) == 0
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
|
@ -8,7 +6,6 @@ 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 parents
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from testing.util import git_commit
|
||||
|
||||
skip_win32 = pytest.mark.skipif(
|
||||
sys.platform == 'win32',
|
||||
|
|
@ -88,7 +85,7 @@ def test_file_conflicts_with_committed_file(temp_git_dir):
|
|||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.join('f.py').write("print('hello world')")
|
||||
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')")
|
||||
cmd_output('git', 'add', 'F.py')
|
||||
|
|
@ -101,7 +98,7 @@ 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')
|
||||
cmd_output('git', 'commit', '--no-gpg-sign', '-n', '-m', 'Add f.py')
|
||||
|
||||
temp_git_dir.join('DIR').write('foo')
|
||||
cmd_output('git', 'add', '-A')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_docstring_first import check_docstring_first
|
||||
|
|
@ -17,7 +15,7 @@ TESTS = (
|
|||
b'from __future__ import unicode_literals\n'
|
||||
b'"foo"\n',
|
||||
1,
|
||||
'{filename}:2: Module docstring appears after code '
|
||||
'{filename}:2 Module docstring appears after code '
|
||||
'(code seen on line 1).\n',
|
||||
),
|
||||
# Test double docstring
|
||||
|
|
@ -26,7 +24,7 @@ TESTS = (
|
|||
b'from __future__ import absolute_import\n'
|
||||
b'"fake docstring"\n',
|
||||
1,
|
||||
'{filename}:3: Multiple module docstrings '
|
||||
'{filename}:3 Multiple module docstrings '
|
||||
'(first docstring on line 1).\n',
|
||||
),
|
||||
# Test multiple lines of code above
|
||||
|
|
@ -35,7 +33,7 @@ TESTS = (
|
|||
b'import sys\n'
|
||||
b'"docstring"\n',
|
||||
1,
|
||||
'{filename}:3: Module docstring appears after code '
|
||||
'{filename}:3 Module docstring appears after code '
|
||||
'(code seen on line 1).\n',
|
||||
),
|
||||
# String literals in expressions are ok.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
from pre_commit_hooks.check_json import main
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
|
@ -8,7 +6,6 @@ import pytest
|
|||
from pre_commit_hooks.check_merge_conflict import main
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from testing.util import get_resource_path
|
||||
from testing.util import git_commit
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -23,19 +20,19 @@ def f1_is_a_conflict_file(tmpdir):
|
|||
with repo1.as_cwd():
|
||||
repo1_f1.ensure()
|
||||
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))
|
||||
|
||||
# Commit in mainline
|
||||
# Commit in master
|
||||
with repo1.as_cwd():
|
||||
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
|
||||
with repo2.as_cwd():
|
||||
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)
|
||||
# We should end up in a merge conflict!
|
||||
f1 = repo2_f1.read()
|
||||
|
|
@ -78,20 +75,20 @@ def repository_pending_merge(tmpdir):
|
|||
with repo1.as_cwd():
|
||||
repo1_f1.ensure()
|
||||
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))
|
||||
|
||||
# Commit in mainline
|
||||
# Commit in master
|
||||
with repo1.as_cwd():
|
||||
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
|
||||
with repo2.as_cwd():
|
||||
repo2_f2.write('child\n')
|
||||
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')
|
||||
# We should end up in a pending merge
|
||||
assert repo2_f1.read() == 'parent\n'
|
||||
|
|
@ -101,18 +98,12 @@ def repository_pending_merge(tmpdir):
|
|||
|
||||
|
||||
@pytest.mark.usefixtures('f1_is_a_conflict_file')
|
||||
def test_merge_conflicts_git(capsys):
|
||||
def test_merge_conflicts_git():
|
||||
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(
|
||||
'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):
|
||||
repository_pending_merge.join('f2').write_binary(contents)
|
||||
|
|
@ -143,15 +134,3 @@ def test_care_when_assumed_merge(tmpdir):
|
|||
f = tmpdir.join('README.md')
|
||||
f.write_binary(b'problem\n=======\n')
|
||||
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,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks.check_toml import main
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks.check_vcs_permalinks import main
|
||||
|
||||
|
||||
|
|
@ -16,9 +14,9 @@ def test_passing(tmpdir):
|
|||
# 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
|
||||
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
|
||||
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),))
|
||||
|
||||
|
|
@ -26,15 +24,17 @@ def test_passing(tmpdir):
|
|||
def test_failing(tmpdir, capsys):
|
||||
with tmpdir.as_cwd():
|
||||
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/master/foo#L1\n'
|
||||
b'https://example.com/asottile/test/blob/main/foo#L1\n',
|
||||
)
|
||||
|
||||
assert main(('f.txt', '--additional-github-domain', 'example.com'))
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == (
|
||||
'f.txt:1:https://github.com/asottile/test/blob/main/foo#L1\n'
|
||||
'f.txt:2:https://example.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/master/foo#L1\n'
|
||||
'f.txt:3:https://example.com/asottile/test/blob/main/foo#L1\n'
|
||||
'\n'
|
||||
'Non-permanent github link detected.\n'
|
||||
'On any page on github press [y] to load a permalink.\n'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_xml import main
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_yaml import main
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
|
||||
from pre_commit_hooks.debug_statement_hook import Debug
|
||||
|
|
@ -55,9 +53,7 @@ def test_non_utf8_file(tmpdir):
|
|||
assert main((str(f_py),)) == 0
|
||||
|
||||
|
||||
def test_py37_breakpoint(tmpdir, capsys):
|
||||
def test_py37_breakpoint(tmpdir):
|
||||
f_py = tmpdir.join('f.py')
|
||||
f_py.write('def f():\n breakpoint()\n')
|
||||
assert main((str(f_py),)) == 1
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == f'{f_py}:2:4: breakpoint called\n'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
|
@ -7,7 +5,6 @@ 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'
|
||||
|
|
@ -26,7 +23,9 @@ def repo_with_destroyed_symlink(tmpdir):
|
|||
with open(TEST_FILE, 'w') as f:
|
||||
print('some random content', file=f)
|
||||
subprocess.check_call(('git', 'add', '.'))
|
||||
git_commit('-m', 'initial')
|
||||
subprocess.check_call(
|
||||
('git', 'commit', '--no-gpg-sign', '-m', 'initial'),
|
||||
)
|
||||
assert b'120000 ' in subprocess.check_output(
|
||||
('git', 'cat-file', '-p', 'HEAD^{tree}'),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.detect_private_key import main
|
||||
|
|
@ -12,8 +10,6 @@ TESTS = (
|
|||
(b'-----BEGIN OPENSSH PRIVATE KEY-----', 1),
|
||||
(b'PuTTY-User-Key-File-2: ssh-rsa', 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-dsa DATA', 0),
|
||||
# Some arbitrary binary data
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.file_contents_sorter import FAIL
|
||||
|
|
@ -10,9 +8,7 @@ from pre_commit_hooks.file_contents_sorter import PASS
|
|||
@pytest.mark.parametrize(
|
||||
('input_s', 'argv', 'expected_retval', 'output'),
|
||||
(
|
||||
(b'', [], PASS, b''),
|
||||
(b'\n', [], FAIL, b''),
|
||||
(b'\n\n', [], FAIL, b''),
|
||||
(b'', [], FAIL, b'\n'),
|
||||
(b'lonesome\n', [], PASS, b'lonesome\n'),
|
||||
(b'missing_newline', [], FAIL, b'missing_newline\n'),
|
||||
(b'newline\nmissing', [], FAIL, b'missing\nnewline\n'),
|
||||
|
|
@ -67,6 +63,18 @@ from pre_commit_hooks.file_contents_sorter import PASS
|
|||
FAIL,
|
||||
b'Fie\nFoe\nfee\nfum\n',
|
||||
),
|
||||
(
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
['--unique', '--ignore-case'],
|
||||
PASS,
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
),
|
||||
(
|
||||
b'fee\nfee\nFie\nFoe\nfum\n',
|
||||
['--unique', '--ignore-case'],
|
||||
FAIL,
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_integration(input_s, argv, expected_retval, output, tmpdir):
|
||||
|
|
@ -77,24 +85,3 @@ def test_integration(input_s, argv, expected_retval, output, tmpdir):
|
|||
|
||||
assert path.read_binary() == output
|
||||
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,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks import fix_byte_order_marker
|
||||
|
||||
|
||||
|
|
|
|||
159
tests/fix_encoding_pragma_test.py
Normal file
159
tests/fix_encoding_pragma_test.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
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((str(path),)) == 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((str(path),)) == 0
|
||||
|
||||
|
||||
def test_integration_remove(tmpdir):
|
||||
path = tmpdir.join('foo.py')
|
||||
path.write_binary(b'# -*- coding: utf-8 -*-\nx = 1\n')
|
||||
|
||||
assert main((str(path), '--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((str(path), '--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'),
|
||||
(
|
||||
('# 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((str(f), '--pragma', pragma)) == 1
|
||||
assert f.read() == '# coding: utf-8\nx = 1\n'
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == f'Added `# coding: utf-8` to {str(f)}\n'
|
||||
|
||||
|
||||
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((str(f),))
|
||||
|
||||
|
||||
def test_crfl_adds(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write_binary(b'x = 1\r\n')
|
||||
assert main((str(f),))
|
||||
assert f.read_binary() == b'# -*- coding: utf-8 -*-\r\nx = 1\r\n'
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.forbid_new_submodules import main
|
||||
from testing.util import git_commit
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def git_dir_with_git_dir(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
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'))
|
||||
git_commit('--allow-empty', '-m', 'init', cwd=str(tmpdir.join('foo')))
|
||||
subprocess.check_call(
|
||||
('git', 'commit', '-m', 'init', '--allow-empty', '--no-gpg-sign'),
|
||||
cwd=str(tmpdir.join('foo')),
|
||||
)
|
||||
yield
|
||||
|
||||
|
||||
|
|
@ -31,24 +31,7 @@ def git_dir_with_git_dir(tmpdir):
|
|||
)
|
||||
def test_main_new_submodule(git_dir_with_git_dir, capsys, cmd):
|
||||
subprocess.check_call(cmd)
|
||||
assert main(('random_non-related_file',)) == 0
|
||||
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
|
||||
assert main() == 1
|
||||
out, _ = capsys.readouterr()
|
||||
assert out.startswith('foo: new submodule introduced\n')
|
||||
|
||||
|
|
@ -56,4 +39,4 @@ def test_main_new_submodule_committed(git_dir_with_git_dir, capsys):
|
|||
def test_main_no_new_submodule(git_dir_with_git_dir):
|
||||
open('test.py', 'a+').close()
|
||||
subprocess.check_call(('git', 'add', 'test.py'))
|
||||
assert main(('test.py',)) == 0
|
||||
assert main() == 0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.mixed_line_ending import main
|
||||
|
|
|
|||
|
|
@ -1,23 +1,20 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.no_commit_to_branch import is_on_branch
|
||||
from pre_commit_hooks.no_commit_to_branch import main
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from testing.util import git_commit
|
||||
|
||||
|
||||
def test_other_branch(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
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):
|
||||
with temp_git_dir.as_cwd():
|
||||
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):
|
||||
|
|
@ -26,10 +23,9 @@ def test_multi_branch_fail(temp_git_dir):
|
|||
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():
|
||||
cmd_output('git', 'checkout', '-b', 'branchname')
|
||||
assert is_on_branch({'branchname'}) is True
|
||||
assert is_on_branch({'master'}) is True
|
||||
|
||||
|
||||
def test_main_branch_call(temp_git_dir):
|
||||
|
|
@ -51,11 +47,11 @@ def test_branch_pattern_fail(temp_git_dir):
|
|||
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):
|
||||
with temp_git_dir.as_cwd():
|
||||
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):
|
||||
|
|
@ -66,7 +62,7 @@ def test_main_default_call(temp_git_dir):
|
|||
|
||||
def test_not_on_a_branch(temp_git_dir):
|
||||
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()
|
||||
cmd_output('git', 'checkout', head)
|
||||
# we're not on a branch!
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
|
@ -82,24 +80,6 @@ def test_autofix_main(tmpdir):
|
|||
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():
|
||||
ret = main((
|
||||
'--top-keys=alist', get_resource_path('pretty_formatted_json.json'),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks.check_yaml import yaml
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.removed import main
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.requirements_txt_fixer import FAIL
|
||||
|
|
@ -68,12 +66,6 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement
|
|||
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'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n',
|
||||
|
|
@ -82,8 +74,6 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement
|
|||
),
|
||||
(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'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',
|
||||
FAIL,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
|
@ -37,12 +35,6 @@ TESTS = (
|
|||
1,
|
||||
),
|
||||
('"foo""bar"', "'foo''bar'", 1),
|
||||
pytest.param(
|
||||
"f'hello{\"world\"}'",
|
||||
"f'hello{\"world\"}'",
|
||||
0,
|
||||
id='ignore nested fstrings',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
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():
|
||||
ret = main(['--django', 'foo_test.py', 'test_bar.py', 'test_baz.py'])
|
||||
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,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.trailing_whitespace_fixer import main
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.util import CalledProcessError
|
||||
|
|
|
|||
4
tox.ini
4
tox.ini
|
|
@ -1,5 +1,5 @@
|
|||
[tox]
|
||||
envlist = py,pre-commit
|
||||
envlist = py36,py37,py38,pypy3,pre-commit
|
||||
|
||||
[testenv]
|
||||
deps = -rrequirements-dev.txt
|
||||
|
|
@ -11,7 +11,7 @@ setenv =
|
|||
commands =
|
||||
coverage erase
|
||||
coverage run -m pytest {posargs:tests}
|
||||
coverage report
|
||||
coverage report --fail-under 100
|
||||
|
||||
[testenv:pre-commit]
|
||||
skip_install = true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue