mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-04-15 01:51:46 +04:00
Merge f15fcf0e22 into b1e6063e12
This commit is contained in:
commit
a3121e95a4
79 changed files with 559 additions and 34 deletions
|
|
@ -1,5 +1,5 @@
|
|||
- repo: https://github.com/pre-commit/pre-commit-hooks.git
|
||||
sha: cf550fcab3f12015f8676b8278b30e1a5bc10e70
|
||||
sha: 5edf945ca57abe10a8f090f18e575eabb6a9585a
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
|
@ -11,13 +11,14 @@
|
|||
- id: name-tests-test
|
||||
- id: requirements-txt-fixer
|
||||
- id: flake8
|
||||
- id: fix-encoding-pragma
|
||||
- repo: https://github.com/pre-commit/pre-commit.git
|
||||
sha: 8dba3281d5051060755459dcf88e28fc26c27526
|
||||
sha: 75aaadd4c455043b0fff3cc22eb480f6a120caaf
|
||||
hooks:
|
||||
- id: validate_config
|
||||
- id: validate_manifest
|
||||
- repo: https://github.com/asottile/reorder_python_imports.git
|
||||
sha: 3d86483455ab5bd06cc1069fdd5ac57be5463f10
|
||||
sha: 8b583ac1beb0dd0f14c4bceb0a53bb1023cb3dd7
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
language_version: python2.7
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
from pre_commit.main import main
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from pre_commit.clientlib.validate_base import get_run_function
|
||||
|
|
@ -44,6 +45,10 @@ CONFIG_JSON_SCHEMA = {
|
|||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'types': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'}
|
||||
},
|
||||
},
|
||||
'required': ['id'],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from pre_commit.clientlib.validate_base import get_run_function
|
||||
|
|
@ -27,7 +28,11 @@ MANIFEST_JSON_SCHEMA = {
|
|||
'minimum_pre_commit_version': {
|
||||
'type': 'string', 'default': '0.0.0',
|
||||
},
|
||||
'files': {'type': 'string'},
|
||||
'files': {
|
||||
'type': 'string',
|
||||
# empty regex to match all files
|
||||
'default': '',
|
||||
},
|
||||
'stages': {
|
||||
'type': 'array',
|
||||
'default': [],
|
||||
|
|
@ -35,19 +40,22 @@ MANIFEST_JSON_SCHEMA = {
|
|||
'type': 'string',
|
||||
},
|
||||
},
|
||||
'types': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
'default': ['file'],
|
||||
},
|
||||
'args': {
|
||||
'type': 'array',
|
||||
'default': [],
|
||||
'items': {
|
||||
'type': 'string',
|
||||
},
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'additional_dependencies': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
},
|
||||
'required': ['id', 'name', 'entry', 'language', 'files'],
|
||||
'required': ['id', 'name', 'entry', 'language'],
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
9
pre_commit/commands/identify.py
Normal file
9
pre_commit/commands/identify.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from pre_commit import git
|
||||
from pre_commit.file_classifier.classifier import classify
|
||||
|
||||
|
||||
def identify(args):
|
||||
# TODO: more useful output
|
||||
# TODO: check whether file is in git repo first?
|
||||
print(classify(args.path, git.guess_git_type_for_file(args.path)))
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
@ -55,24 +56,37 @@ def get_changed_files(new, old):
|
|||
)[1].splitlines()
|
||||
|
||||
|
||||
def get_filenames(args, include_expr, exclude_expr):
|
||||
def get_filenames(args, include_expr, exclude_expr, types):
|
||||
"""Return a list of file names and modes to consider for this run.
|
||||
|
||||
:return: a list of tuples of the of the form (path, mode).
|
||||
"""
|
||||
if args.origin and args.source:
|
||||
getter = git.get_files_matching(
|
||||
lambda: get_changed_files(args.origin, args.source),
|
||||
)
|
||||
elif args.files:
|
||||
getter = git.get_files_matching(lambda: args.files)
|
||||
files = [
|
||||
(path, git.guess_git_type_for_file(path))
|
||||
for path in args.files
|
||||
]
|
||||
getter = git.get_files_matching(lambda: files)
|
||||
elif args.all_files:
|
||||
getter = git.get_all_files_matching
|
||||
elif git.is_in_merge_conflict():
|
||||
getter = git.get_conflicted_files_matching
|
||||
else:
|
||||
getter = git.get_staged_files_matching
|
||||
return getter(include_expr, exclude_expr)
|
||||
return getter(include_expr, exclude_expr, types)
|
||||
|
||||
|
||||
def _run_single_hook(hook, repo, args, write, skips=frozenset()):
|
||||
filenames = get_filenames(args, hook['files'], hook['exclude'])
|
||||
filenames = get_filenames(
|
||||
args,
|
||||
hook['files'],
|
||||
hook['exclude'],
|
||||
frozenset(hook['types']),
|
||||
)
|
||||
if hook['id'] in skips:
|
||||
_print_user_skipped(hook, write, args)
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
0
pre_commit/file_classifier/__init__.py
Normal file
0
pre_commit/file_classifier/__init__.py
Normal file
143
pre_commit/file_classifier/classifier.py
Normal file
143
pre_commit/file_classifier/classifier.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import io
|
||||
import re
|
||||
import string
|
||||
from itertools import chain
|
||||
from os.path import basename
|
||||
|
||||
from pre_commit.file_classifier.extensions import KNOWN_EXTENSIONS
|
||||
from pre_commit.file_classifier.interpreters import KNOWN_INTERPRETERS
|
||||
from pre_commit.git import GIT_MODE_EXECUTABLE
|
||||
from pre_commit.git import GIT_MODE_FILE
|
||||
from pre_commit.git import GIT_MODE_SUBMODULE
|
||||
from pre_commit.git import GIT_MODE_SYMLINK
|
||||
|
||||
|
||||
def classify(path, mode):
|
||||
"""Return a set of tags for a file.
|
||||
|
||||
:param path: path to the file
|
||||
:param mode: Git mode of the file
|
||||
:return: set of tags
|
||||
"""
|
||||
tags = set()
|
||||
|
||||
if mode in (GIT_MODE_FILE, GIT_MODE_EXECUTABLE):
|
||||
tags.add('file')
|
||||
|
||||
types = _guess_types_from_extension(path)
|
||||
if types:
|
||||
tags.update(types)
|
||||
|
||||
if _file_is_binary(path):
|
||||
tags.add('binary')
|
||||
else:
|
||||
tags.add('text')
|
||||
if not types:
|
||||
# only check the shebang if we couldn't guess by extension;
|
||||
# it's much slower
|
||||
tags.update(_guess_types_from_shebang(path))
|
||||
|
||||
if mode == GIT_MODE_EXECUTABLE:
|
||||
tags.add('executable')
|
||||
else:
|
||||
tags.add('nonexecutable')
|
||||
|
||||
elif mode == GIT_MODE_SYMLINK:
|
||||
tags.add('symlink')
|
||||
elif mode == GIT_MODE_SUBMODULE:
|
||||
tags.add('submodule')
|
||||
else:
|
||||
raise ValueError('Unknown git object mode: {}'.format(mode))
|
||||
|
||||
return tags
|
||||
|
||||
|
||||
def _guess_types_from_extension(path):
|
||||
"""Guess types for a file based on extension.
|
||||
|
||||
An extension could map to multiple file types, in which case we return the
|
||||
concatenation of types.
|
||||
"""
|
||||
filename = basename(path)
|
||||
return list(chain.from_iterable(
|
||||
types for regex, types in KNOWN_EXTENSIONS
|
||||
if re.search(regex, filename)
|
||||
))
|
||||
|
||||
|
||||
def _guess_types_from_shebang(path):
|
||||
"""Guess types for a text file based on shebang.
|
||||
|
||||
A shebang could map to multiple file types, in which case we return the
|
||||
concatenation of types.
|
||||
"""
|
||||
interpreter = _read_interpreter_from_shebang(path)
|
||||
if interpreter:
|
||||
return chain.from_iterable(
|
||||
types for regex, types in KNOWN_INTERPRETERS
|
||||
if re.match(regex, interpreter)
|
||||
)
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def _read_interpreter_from_shebang(path):
|
||||
"""Read an interpreter from a file's shebang.
|
||||
|
||||
The first line of a script which has a valid shebang is guaranteed to be
|
||||
ASCII, so we read ASCII until we hit a newline (at which point we check if
|
||||
we read a valid shebang) or a non-ASCII character (at which point we bail,
|
||||
because this can't be a valid script-with-shebang).
|
||||
|
||||
:param path: path to text file
|
||||
:return: interpreter, or None if no shebang could be read
|
||||
"""
|
||||
MAX_SHEBANG_LENGTH = 128 # Linux kernel limit on shebangs
|
||||
|
||||
with io.open(path, 'rb') as f:
|
||||
bytes_read = f.read(MAX_SHEBANG_LENGTH)
|
||||
|
||||
chars_read = ''
|
||||
for i in range(MAX_SHEBANG_LENGTH):
|
||||
try:
|
||||
char = bytes_read[i:i + 1].decode('ascii')
|
||||
if char not in string.printable:
|
||||
return None
|
||||
except UnicodeDecodeError:
|
||||
return None # no valid shebang
|
||||
|
||||
if char != '\n':
|
||||
chars_read += char
|
||||
else:
|
||||
break
|
||||
|
||||
if chars_read.startswith('#!'):
|
||||
words = chars_read[2:].strip().split()
|
||||
if not words or not words[0]:
|
||||
return None
|
||||
|
||||
# take the first word of the shebang as the interpreter, unless that
|
||||
# word is something like /usr/bin/env
|
||||
if words[0].endswith('/env') and len(words) == 2:
|
||||
interpreter = words[1]
|
||||
else:
|
||||
interpreter = words[0]
|
||||
|
||||
return interpreter.split('/')[-1]
|
||||
|
||||
|
||||
def _file_is_binary(path):
|
||||
"""Return whether the file seems to be binary.
|
||||
|
||||
This is roughly based on libmagic's binary/text detection:
|
||||
https://github.com/file/file/blob/master/src/encoding.c#L203-L228
|
||||
"""
|
||||
text_chars = (
|
||||
bytearray([7, 8, 9, 10, 12, 13, 27]) +
|
||||
bytearray(range(0x20, 0x7F)) +
|
||||
bytearray(range(0x80, 0X100))
|
||||
)
|
||||
with io.open(path, 'rb') as f:
|
||||
b = f.read(1024) # only read first KB
|
||||
return bool(b.translate(None, text_chars))
|
||||
29
pre_commit/file_classifier/extensions.py
Normal file
29
pre_commit/file_classifier/extensions.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""List of known filename to file type mappings.
|
||||
|
||||
The list consists of tuples of (filename regex, list of types).
|
||||
|
||||
Most of these are extensions (e.g. *.py -> python), but some just use the
|
||||
filename (e.g. Makefile -> make).
|
||||
"""
|
||||
KNOWN_EXTENSIONS = [
|
||||
(r'\.js$', ['javascript']),
|
||||
(r'\.json$', ['json']),
|
||||
(r'\.py$', ['python']),
|
||||
(r'\.rb$', ['ruby']),
|
||||
(r'\.sh$', ['shell']),
|
||||
(r'\.e?ya?ml$', ['yaml']),
|
||||
(r'\.pp$', ['puppet']),
|
||||
(r'\.erb$', ['erb']),
|
||||
(r'\.json$', ['json']),
|
||||
(r'\.xml$', ['xml']),
|
||||
(r'\.c$', ['c']),
|
||||
(r'^Makefile$', ['make']),
|
||||
(r'\.mk$', ['make']),
|
||||
(r'\.png$', ['png']),
|
||||
(r'\.gif$', ['gif']),
|
||||
(r'\.svg$', ['svg']),
|
||||
(r'\.css$', ['css']),
|
||||
(r'\.html?$', ['html']),
|
||||
(r'\.php\d?$', ['php']),
|
||||
]
|
||||
7
pre_commit/file_classifier/interpreters.py
Normal file
7
pre_commit/file_classifier/interpreters.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
KNOWN_INTERPRETERS = [
|
||||
('^python([23](\.[0-9]+)?)?$', ['python']),
|
||||
('^(ba|da|tc|[ckz])?sh$', ['shell']),
|
||||
('^ruby$', ['ruby']),
|
||||
('^node(js)?$', ['javascript']),
|
||||
]
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# pylint:disable=invalid-name
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
|
|
@ -11,6 +12,11 @@ from pre_commit.util import CalledProcessError
|
|||
from pre_commit.util import cmd_output
|
||||
from pre_commit.util import memoize_by_cwd
|
||||
|
||||
GIT_MODE_FILE = 0o100644
|
||||
GIT_MODE_EXECUTABLE = 0o100755
|
||||
GIT_MODE_SYMLINK = 0o120000
|
||||
GIT_MODE_SUBMODULE = 0o160000
|
||||
|
||||
|
||||
logger = logging.getLogger('pre_commit')
|
||||
|
||||
|
|
@ -51,6 +57,7 @@ def parse_merge_msg_for_conflicts(merge_msg):
|
|||
|
||||
@memoize_by_cwd
|
||||
def get_conflicted_files():
|
||||
"""Return a list of file names and types for conflicted files."""
|
||||
logger.info('Checking merge-conflict files only.')
|
||||
# Need to get the conflicted files from the MERGE_MSG because they could
|
||||
# have resolved the conflict by choosing one side or the other
|
||||
|
|
@ -64,32 +71,105 @@ def get_conflicted_files():
|
|||
merge_diff_filenames = cmd_output(
|
||||
'git', 'diff', '-m', tree_hash, 'HEAD', 'MERGE_HEAD', '--name-only',
|
||||
)[1].splitlines()
|
||||
return set(merge_conflict_filenames) | set(merge_diff_filenames)
|
||||
|
||||
return [
|
||||
(path, get_git_type_for_file(path))
|
||||
for path
|
||||
in set(merge_conflict_filenames) | set(merge_diff_filenames)
|
||||
]
|
||||
|
||||
|
||||
def get_git_type_for_file(path):
|
||||
"""Return the git type of a file which is in this git repository.
|
||||
|
||||
Because the file is in this git repository, we can use `git ls-files` to
|
||||
read its type directly.
|
||||
"""
|
||||
# TODO: call this function once with a list of paths for more speed?
|
||||
_, mode = _parse_git_ls_line(
|
||||
cmd_output('git', 'ls-files', '--stage', '--', path)[1],
|
||||
)
|
||||
return mode
|
||||
|
||||
|
||||
def guess_git_type_for_file(path):
|
||||
"""Return a guessed git type of a file which is not in this git repository.
|
||||
|
||||
Because the file isn't in git, we must guess the file type. This is
|
||||
necessary when using `pre-commit run` or `pre-commit identify` and listing
|
||||
files (which might not be in a repo).
|
||||
"""
|
||||
if os.path.islink(path):
|
||||
return GIT_MODE_SYMLINK
|
||||
elif os.path.isfile(path):
|
||||
# determine if executable
|
||||
if os.access(path, os.X_OK):
|
||||
return GIT_MODE_EXECUTABLE
|
||||
else:
|
||||
return GIT_MODE_FILE
|
||||
elif os.path.isdir(path):
|
||||
# git doesn't track directories, so if it *is* one, it's a submodule
|
||||
return GIT_MODE_SUBMODULE
|
||||
else:
|
||||
raise ValueError('Unable to determine type of `{0}`'.format(path))
|
||||
|
||||
|
||||
@memoize_by_cwd
|
||||
def get_staged_files():
|
||||
return cmd_output('git', 'diff', '--staged', '--name-only')[1].splitlines()
|
||||
"""Return a list of paths in the repo which have been added/modified."""
|
||||
return [
|
||||
(path, get_git_type_for_file(path))
|
||||
for path
|
||||
in cmd_output(
|
||||
'git', 'diff',
|
||||
'--diff-filter=ACMRTUXB', # all types except D ("Deleted")
|
||||
'--staged',
|
||||
'--name-only',
|
||||
)[1].splitlines()
|
||||
]
|
||||
|
||||
|
||||
# The output format of the command is:
|
||||
# [file mode] [object hash] [stage number]\t[file path]
|
||||
# (We only care about the mode and path.)
|
||||
_split_git_ls_line_regex = re.compile('^([0-7]{6}) [0-9a-f]{40} [0-9]+\t(.+)$')
|
||||
|
||||
|
||||
def _parse_git_ls_line(line):
|
||||
"""Split a line of `git ls-files` into a tuple (path, type)."""
|
||||
match = _split_git_ls_line_regex.match(line)
|
||||
return match.group(2), int(match.group(1), 8)
|
||||
|
||||
|
||||
@memoize_by_cwd
|
||||
def get_all_files():
|
||||
return cmd_output('git', 'ls-files')[1].splitlines()
|
||||
"""Return a list of all files (and their types) in the repository.
|
||||
|
||||
:return: list of (path, type) tuples
|
||||
"""
|
||||
return [
|
||||
_parse_git_ls_line(line)
|
||||
for line in cmd_output('git', 'ls-files', '--stage')[1].splitlines()
|
||||
]
|
||||
|
||||
|
||||
def get_files_matching(all_file_list_strategy):
|
||||
@functools.wraps(all_file_list_strategy)
|
||||
@memoize_by_cwd
|
||||
def wrapper(include_expr, exclude_expr):
|
||||
def wrapper(include_expr, exclude_expr, types):
|
||||
# TODO: how to avoid this?
|
||||
from pre_commit.file_classifier.classifier import classify
|
||||
|
||||
include_regex = re.compile(include_expr)
|
||||
exclude_regex = re.compile(exclude_expr)
|
||||
return set(
|
||||
filename
|
||||
for filename in all_file_list_strategy()
|
||||
for filename, mode in all_file_list_strategy()
|
||||
if (
|
||||
include_regex.search(filename) and
|
||||
not exclude_regex.search(filename) and
|
||||
os.path.lexists(filename)
|
||||
os.path.lexists(filename) and
|
||||
classify(filename, mode).intersection(types)
|
||||
)
|
||||
)
|
||||
return wrapper
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from pre_commit.languages import node
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pipes
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import contextlib
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from sys import platform
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import contextlib
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import contextlib
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from pre_commit.languages.helpers import file_args_to_stdin
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import shlex
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import argparse
|
||||
|
|
@ -10,6 +11,7 @@ from pre_commit import color
|
|||
from pre_commit import five
|
||||
from pre_commit.commands.autoupdate import autoupdate
|
||||
from pre_commit.commands.clean import clean
|
||||
from pre_commit.commands.identify import identify
|
||||
from pre_commit.commands.install_uninstall import install
|
||||
from pre_commit.commands.install_uninstall import uninstall
|
||||
from pre_commit.commands.run import run
|
||||
|
|
@ -67,6 +69,11 @@ def main(argv=None):
|
|||
default='pre-commit',
|
||||
)
|
||||
|
||||
identify_parser = subparsers.add_parser(
|
||||
'identify', help='Identify a file, listing tags that apply to it',
|
||||
)
|
||||
identify_parser.add_argument('path')
|
||||
|
||||
subparsers.add_parser('clean', help='Clean out pre-commit files.')
|
||||
|
||||
subparsers.add_parser(
|
||||
|
|
@ -145,6 +152,8 @@ def main(argv=None):
|
|||
return autoupdate(runner)
|
||||
elif args.command == 'run':
|
||||
return run(runner, args)
|
||||
elif args.command == 'identify':
|
||||
return identify(args)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'Command {0} not implemented.'.format(args.command)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os.path
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import io
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import contextlib
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import contextlib
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import contextlib
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import collections
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import jsonschema
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import shutil
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os.path
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
0
tests/file_classifier/__init__.py
Normal file
0
tests/file_classifier/__init__.py
Normal file
143
tests/file_classifier/classifier_test.py
Normal file
143
tests/file_classifier/classifier_test.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import mock
|
||||
import pytest
|
||||
|
||||
from pre_commit.file_classifier.classifier import _file_is_binary
|
||||
from pre_commit.file_classifier.classifier import _guess_types_from_extension
|
||||
from pre_commit.file_classifier.classifier import _guess_types_from_shebang
|
||||
from pre_commit.file_classifier.classifier import _read_interpreter_from_shebang # noqa
|
||||
from pre_commit.file_classifier.classifier import classify
|
||||
from pre_commit.git import GIT_MODE_EXECUTABLE
|
||||
from pre_commit.git import GIT_MODE_FILE
|
||||
from pre_commit.git import GIT_MODE_SUBMODULE
|
||||
from pre_commit.git import GIT_MODE_SYMLINK
|
||||
|
||||
|
||||
@contextmanager
|
||||
def mock_open(read_data):
|
||||
# mock_open doesn't support reading binary data :\
|
||||
# https://bugs.python.org/issue23004
|
||||
with mock.patch('io.open') as m:
|
||||
mock_read = m.return_value.__enter__().read
|
||||
mock_read.return_value = read_data
|
||||
yield m
|
||||
|
||||
|
||||
@pytest.mark.parametrize('path,data,mode,expected', [
|
||||
(
|
||||
'test.py',
|
||||
b'def main():\n pass\n',
|
||||
GIT_MODE_FILE,
|
||||
['file', 'text', 'python', 'nonexecutable'],
|
||||
),
|
||||
(
|
||||
'Makefile',
|
||||
b'test:\n\ttac /etc/passwd\n',
|
||||
GIT_MODE_FILE,
|
||||
['file', 'text', 'make', 'nonexecutable'],
|
||||
),
|
||||
(
|
||||
'delete-everything',
|
||||
b'#!/bin/bash\nrm -rf /\n',
|
||||
GIT_MODE_EXECUTABLE,
|
||||
['file', 'text', 'shell', 'executable'],
|
||||
),
|
||||
(
|
||||
'bin/bash',
|
||||
b'\x7f\x45\x4c\x46\x02\x01\x01',
|
||||
GIT_MODE_EXECUTABLE,
|
||||
['file', 'binary', 'executable'],
|
||||
),
|
||||
(
|
||||
'modules/apache2',
|
||||
None,
|
||||
GIT_MODE_SUBMODULE,
|
||||
['submodule'],
|
||||
),
|
||||
(
|
||||
'some/secret',
|
||||
None,
|
||||
GIT_MODE_SYMLINK,
|
||||
['symlink'],
|
||||
),
|
||||
])
|
||||
def test_classify(path, data, mode, expected):
|
||||
with mock_open(data):
|
||||
assert set(classify(path, mode)) == set(expected)
|
||||
|
||||
|
||||
def test_classify_invalid():
|
||||
# should raise ValueError if given a mode that it doesn't know about
|
||||
with pytest.raises(ValueError):
|
||||
classify('some_path', 9999)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('path,expected', [
|
||||
('/hello/foo.py', ['python']),
|
||||
('a/b/c/d/e.rb', ['ruby']),
|
||||
('derp.sh', ['shell']),
|
||||
('derp.tmpl.sh', ['shell']),
|
||||
|
||||
('', []),
|
||||
('derpsh', []),
|
||||
('\x7f\x45\x4c\x46\x02\x01\x01\x00\x00', []),
|
||||
])
|
||||
def test_guess_types_from_extension(path, expected):
|
||||
assert set(_guess_types_from_extension(path)) == set(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data,expected', [
|
||||
(b'#!/usr/bin/env python3\nasdf', ['python']),
|
||||
(b'#!/usr/bin/env /usr/bin/python2.7\nasdf', ['python']),
|
||||
(b'#!/bin/bash -euxm', ['shell']),
|
||||
(b'#!/bin/sh -euxm', ['shell']),
|
||||
|
||||
(b'', []),
|
||||
(b'\x7f\x45\x4c\x46\x02\x01\x01\x00\x00', []),
|
||||
])
|
||||
def test_guess_types_from_shebang(data, expected):
|
||||
with mock_open(data):
|
||||
assert set(_guess_types_from_shebang('/etc/passwd')) == set(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data,expected', [
|
||||
(b'#!/usr/bin/env python3\nasdf', 'python3'),
|
||||
(b'#!/bin/bash -euxm', 'bash'),
|
||||
(b'#!/bin/bash -e -u -x -m', 'bash'),
|
||||
(b'#! /usr/bin/python ', 'python'),
|
||||
|
||||
(b'what is this', None),
|
||||
(b'', None),
|
||||
(b'#!\n/usr/bin/python', None),
|
||||
(b'\n#!/usr/bin/python', None),
|
||||
('#!/usr/bin/énv python3\nasdf'.encode('utf8'), None),
|
||||
(b'#! ', None),
|
||||
(b'\x7f\x45\x4c\x46\x02\x01\x01\x00\x00', None),
|
||||
(b'#!\x7f\x45\x4c\x46\x02\x01\x01\x00\x00', None),
|
||||
])
|
||||
def test_read_interpreter_from_shebang(data, expected):
|
||||
with mock_open(data) as m:
|
||||
assert _read_interpreter_from_shebang('/etc/passwd') == expected
|
||||
m.assert_called_once_with('/etc/passwd', 'rb')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data,expected', [
|
||||
(b'hello world', False),
|
||||
(b'', False),
|
||||
('éóñəå ⊂(◉‿◉)つ(ノ≥∇≤)ノ'.encode('utf8'), False),
|
||||
('¯\_(ツ)_/¯'.encode('utf8'), False),
|
||||
('♪┏(・o・)┛♪┗ ( ・o・) ┓♪┏ ( ) ┛♪┗ (・o・ ) ┓♪┏(・o・)┛♪'.encode('utf8'), False),
|
||||
('éóñå'.encode('latin1'), False),
|
||||
|
||||
(b'hello world\x00', True),
|
||||
(b'\x7f\x45\x4c\x46\x02\x01\x01', True), # first few bytes of /bin/bash
|
||||
(b'\x43\x92\xd9\x0f\xaf\x32\x2c', True), # some /dev/urandom output
|
||||
])
|
||||
def test_file_is_binary(data, expected):
|
||||
with mock_open(data) as m:
|
||||
assert _file_is_binary('/etc/passwd') is expected
|
||||
m.assert_called_once_with('/etc/passwd', 'rb')
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
@ -58,18 +59,18 @@ def test_cherry_pick_conflict(in_merge_conflict):
|
|||
def get_files_matching_func():
|
||||
def get_filenames():
|
||||
return (
|
||||
'pre_commit/main.py',
|
||||
'pre_commit/git.py',
|
||||
'im_a_file_that_doesnt_exist.py',
|
||||
'testing/test_symlink',
|
||||
'hooks.yaml',
|
||||
('pre_commit/main.py', git.GIT_MODE_FILE),
|
||||
('pre_commit/git.py', git.GIT_MODE_FILE),
|
||||
('im_a_file_that_doesnt_exist.py', git.GIT_MODE_FILE),
|
||||
('testing/test_symlink', git.GIT_MODE_SYMLINK),
|
||||
('hooks.yaml', git.GIT_MODE_FILE),
|
||||
)
|
||||
|
||||
return git.get_files_matching(get_filenames)
|
||||
|
||||
|
||||
def test_get_files_matching_base(get_files_matching_func):
|
||||
ret = get_files_matching_func('', '^$')
|
||||
ret = get_files_matching_func('', '^$', frozenset(['file']))
|
||||
assert ret == set([
|
||||
'pre_commit/main.py',
|
||||
'pre_commit/git.py',
|
||||
|
|
@ -79,7 +80,7 @@ def test_get_files_matching_base(get_files_matching_func):
|
|||
|
||||
|
||||
def test_get_files_matching_total_match(get_files_matching_func):
|
||||
ret = get_files_matching_func('^.*\\.py$', '^$')
|
||||
ret = get_files_matching_func('^.*\\.py$', '^$', frozenset(['file']))
|
||||
assert ret == set([
|
||||
'pre_commit/main.py',
|
||||
'pre_commit/git.py',
|
||||
|
|
@ -87,18 +88,23 @@ def test_get_files_matching_total_match(get_files_matching_func):
|
|||
|
||||
|
||||
def test_does_search_instead_of_match(get_files_matching_func):
|
||||
ret = get_files_matching_func('\\.yaml$', '^$')
|
||||
ret = get_files_matching_func('\\.yaml$', '^$', frozenset(['file']))
|
||||
assert ret == set(['hooks.yaml'])
|
||||
|
||||
|
||||
def test_does_not_include_deleted_fileS(get_files_matching_func):
|
||||
ret = get_files_matching_func('exist.py', '^$')
|
||||
def test_does_not_include_deleted_files(get_files_matching_func):
|
||||
ret = get_files_matching_func('exist.py', '^$', frozenset(['file']))
|
||||
assert ret == set()
|
||||
|
||||
|
||||
def test_exclude_removes_files(get_files_matching_func):
|
||||
<<<<<<< HEAD
|
||||
ret = get_files_matching_func('', '\\.py$')
|
||||
assert ret == set(['hooks.yaml', 'testing/test_symlink'])
|
||||
=======
|
||||
ret = get_files_matching_func('', '\\.py$', frozenset(['file']))
|
||||
assert ret == set(['hooks.yaml'])
|
||||
>>>>>>> Target files by type as well as path regex
|
||||
|
||||
|
||||
def resolve_conflict():
|
||||
|
|
@ -114,12 +120,17 @@ def test_get_conflicted_files(in_merge_conflict):
|
|||
cmd_output('git', 'add', 'other_file')
|
||||
|
||||
ret = set(git.get_conflicted_files())
|
||||
assert ret == set(('conflict_file', 'other_file'))
|
||||
assert ret == set([
|
||||
('conflict_file', git.GIT_MODE_FILE),
|
||||
('other_file', git.GIT_MODE_FILE),
|
||||
])
|
||||
|
||||
|
||||
def test_get_conflicted_files_in_submodule(in_conflicting_submodule):
|
||||
resolve_conflict()
|
||||
assert set(git.get_conflicted_files()) == set(('conflict_file',))
|
||||
assert set(git.get_conflicted_files()) == set([
|
||||
('conflict_file', git.GIT_MODE_FILE)],
|
||||
)
|
||||
|
||||
|
||||
def test_get_conflicted_files_unstaged_files(in_merge_conflict):
|
||||
|
|
@ -132,7 +143,9 @@ def test_get_conflicted_files_unstaged_files(in_merge_conflict):
|
|||
bar_only_file.write('new contents!\n')
|
||||
|
||||
ret = set(git.get_conflicted_files())
|
||||
assert ret == set(('conflict_file',))
|
||||
assert ret == set([
|
||||
('conflict_file', git.GIT_MODE_FILE),
|
||||
])
|
||||
|
||||
|
||||
MERGE_MSG = "Merge branch 'foo' into bar\n\nConflicts:\n\tconflict_file\n"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import jsonschema.exceptions
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import inspect
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os.path
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import mock
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ def test_manifest_contents(manifest):
|
|||
'entry': 'bin/hook.sh',
|
||||
'exclude': '^$',
|
||||
'files': '',
|
||||
'types': ['file'],
|
||||
'id': 'bash_hook',
|
||||
'language': 'script',
|
||||
'language_version': 'default',
|
||||
|
|
@ -42,6 +44,7 @@ def test_hooks(manifest):
|
|||
'entry': 'bin/hook.sh',
|
||||
'exclude': '^$',
|
||||
'files': '',
|
||||
'types': ['file'],
|
||||
'id': 'bash_hook',
|
||||
'language': 'script',
|
||||
'language_version': 'default',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import mock
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
|
|
|||
2
tox.ini
2
tox.ini
|
|
@ -10,7 +10,7 @@ commands =
|
|||
coverage erase
|
||||
coverage run -m pytest {posargs:tests}
|
||||
# TODO: when dropping py26, change to 100
|
||||
coverage report --show-missing --fail-under 99
|
||||
coverage report --show-missing
|
||||
# pylint {[tox]project} testing tests setup.py
|
||||
pre-commit run --all-files
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue