mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-01-14 21:10:27 -06:00
Move most of the actual hook script into pre-commit hook-impl
This commit is contained in:
@@ -1,197 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
"""File generated by pre-commit: https://pre-commit.com"""
|
||||
import distutils.spawn
|
||||
# File generated by pre-commit: https://pre-commit.com
|
||||
# ID: 138fd403232d2ddd5efb44317e38bf03
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Tuple
|
||||
|
||||
# we try our best, but the shebang of this script is difficult to determine:
|
||||
# - macos doesn't ship with python3
|
||||
# - windows executables are almost always `python.exe`
|
||||
# therefore we continue to support python2 for this small script
|
||||
if sys.version_info < (3, 3):
|
||||
from distutils.spawn import find_executable as which
|
||||
else:
|
||||
from shutil import which
|
||||
|
||||
# work around https://github.com/Homebrew/homebrew-core/issues/30445
|
||||
os.environ.pop('__PYVENV_LAUNCHER__', None)
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
Z40 = '0' * 40
|
||||
ID_HASH = '138fd403232d2ddd5efb44317e38bf03'
|
||||
# start templated
|
||||
CONFIG = ''
|
||||
HOOK_TYPE = ''
|
||||
INSTALL_PYTHON = ''
|
||||
SKIP_ON_MISSING_CONFIG = False
|
||||
ARGS = ['hook-impl']
|
||||
# end templated
|
||||
ARGS.extend(('--hook-dir', os.path.realpath(os.path.dirname(__file__))))
|
||||
ARGS.append('--')
|
||||
ARGS.extend(sys.argv[1:])
|
||||
|
||||
|
||||
class EarlyExit(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class FatalError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _norm_exe(exe: str) -> Tuple[str, ...]:
|
||||
"""Necessary for shebang support on windows.
|
||||
|
||||
roughly lifted from `identify.identify.parse_shebang`
|
||||
"""
|
||||
with open(exe, 'rb') as f:
|
||||
if f.read(2) != b'#!':
|
||||
return ()
|
||||
try:
|
||||
first_line = f.readline().decode()
|
||||
except UnicodeDecodeError:
|
||||
return ()
|
||||
|
||||
cmd = first_line.split()
|
||||
if cmd[0] == '/usr/bin/env':
|
||||
del cmd[0]
|
||||
return tuple(cmd)
|
||||
|
||||
|
||||
def _run_legacy() -> Tuple[int, bytes]:
|
||||
if __file__.endswith('.legacy'):
|
||||
raise SystemExit(
|
||||
f"bug: pre-commit's script is installed in migration mode\n"
|
||||
f'run `pre-commit install -f --hook-type {HOOK_TYPE}` to fix '
|
||||
f'this\n\n'
|
||||
f'Please report this bug at '
|
||||
f'https://github.com/pre-commit/pre-commit/issues',
|
||||
)
|
||||
|
||||
if HOOK_TYPE == 'pre-push':
|
||||
stdin = sys.stdin.buffer.read()
|
||||
else:
|
||||
stdin = b''
|
||||
|
||||
legacy_hook = os.path.join(HERE, f'{HOOK_TYPE}.legacy')
|
||||
if os.access(legacy_hook, os.X_OK):
|
||||
cmd = _norm_exe(legacy_hook) + (legacy_hook,) + tuple(sys.argv[1:])
|
||||
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE if stdin else None)
|
||||
proc.communicate(stdin)
|
||||
return proc.returncode, stdin
|
||||
else:
|
||||
return 0, stdin
|
||||
|
||||
|
||||
def _validate_config() -> None:
|
||||
cmd = ('git', 'rev-parse', '--show-toplevel')
|
||||
top_level = subprocess.check_output(cmd).decode().strip()
|
||||
cfg = os.path.join(top_level, CONFIG)
|
||||
if os.path.isfile(cfg):
|
||||
pass
|
||||
elif SKIP_ON_MISSING_CONFIG or os.getenv('PRE_COMMIT_ALLOW_NO_CONFIG'):
|
||||
print(f'`{CONFIG}` config file not found. Skipping `pre-commit`.')
|
||||
raise EarlyExit()
|
||||
else:
|
||||
raise FatalError(
|
||||
f'No {CONFIG} file was found\n'
|
||||
f'- To temporarily silence this, run '
|
||||
f'`PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\n'
|
||||
f'- To permanently silence this, install pre-commit with the '
|
||||
f'--allow-missing-config option\n'
|
||||
f'- To uninstall pre-commit run '
|
||||
f'`pre-commit uninstall`',
|
||||
)
|
||||
|
||||
|
||||
def _exe() -> Tuple[str, ...]:
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
for exe in (INSTALL_PYTHON, sys.executable):
|
||||
try:
|
||||
if not subprocess.call(
|
||||
(exe, '-c', 'import pre_commit.main'),
|
||||
stdout=devnull, stderr=devnull,
|
||||
):
|
||||
return (exe, '-m', 'pre_commit.main', 'run')
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if distutils.spawn.find_executable('pre-commit'):
|
||||
return ('pre-commit', 'run')
|
||||
|
||||
raise FatalError(
|
||||
'`pre-commit` not found. Did you forget to activate your virtualenv?',
|
||||
)
|
||||
|
||||
|
||||
def _rev_exists(rev: str) -> bool:
|
||||
return not subprocess.call(('git', 'rev-list', '--quiet', rev))
|
||||
|
||||
|
||||
def _pre_push(stdin: bytes) -> Tuple[str, ...]:
|
||||
remote_name = sys.argv[1]
|
||||
remote_url = sys.argv[2]
|
||||
|
||||
opts: Tuple[str, ...] = ()
|
||||
for line in stdin.decode().splitlines():
|
||||
_, local_sha, _, remote_sha = line.split()
|
||||
if local_sha == Z40:
|
||||
continue
|
||||
elif remote_sha != Z40 and _rev_exists(remote_sha):
|
||||
opts = ('--origin', local_sha, '--source', remote_sha)
|
||||
else:
|
||||
# ancestors not found in remote
|
||||
ancestors = subprocess.check_output((
|
||||
'git', 'rev-list', local_sha, '--topo-order', '--reverse',
|
||||
'--not', f'--remotes={remote_name}',
|
||||
)).decode().strip()
|
||||
if not ancestors:
|
||||
continue
|
||||
else:
|
||||
first_ancestor = ancestors.splitlines()[0]
|
||||
cmd = ('git', 'rev-list', '--max-parents=0', local_sha)
|
||||
roots = set(subprocess.check_output(cmd).decode().splitlines())
|
||||
if first_ancestor in roots:
|
||||
# pushing the whole tree including root commit
|
||||
opts = ('--all-files',)
|
||||
else:
|
||||
rev_cmd = ('git', 'rev-parse', f'{first_ancestor}^')
|
||||
source = subprocess.check_output(rev_cmd).decode().strip()
|
||||
opts = ('--origin', local_sha, '--source', source)
|
||||
|
||||
if opts:
|
||||
return (
|
||||
*opts, '--remote-name', remote_name, '--remote-url', remote_url,
|
||||
)
|
||||
else:
|
||||
# An attempt to push an empty changeset
|
||||
raise EarlyExit()
|
||||
|
||||
|
||||
def _opts(stdin: bytes) -> Tuple[str, ...]:
|
||||
fns: Dict[str, Callable[[bytes], Tuple[str, ...]]] = {
|
||||
'prepare-commit-msg': lambda _: ('--commit-msg-filename', sys.argv[1]),
|
||||
'commit-msg': lambda _: ('--commit-msg-filename', sys.argv[1]),
|
||||
'pre-merge-commit': lambda _: (),
|
||||
'pre-commit': lambda _: (),
|
||||
'pre-push': _pre_push,
|
||||
}
|
||||
stage = HOOK_TYPE.replace('pre-', '')
|
||||
return ('--config', CONFIG, '--hook-stage', stage) + fns[HOOK_TYPE](stdin)
|
||||
|
||||
|
||||
if sys.version_info < (3, 7): # https://bugs.python.org/issue25942
|
||||
# this is the python 2.7 implementation
|
||||
def _subprocess_call(cmd: Tuple[str, ...]) -> int:
|
||||
return subprocess.Popen(cmd).wait()
|
||||
DNE = '`pre-commit` not found. Did you forget to activate your virtualenv?'
|
||||
if os.access(INSTALL_PYTHON, os.X_OK):
|
||||
CMD = [INSTALL_PYTHON, '-mpre_commit']
|
||||
elif which('pre-commit'):
|
||||
CMD = ['pre-commit']
|
||||
else:
|
||||
_subprocess_call = subprocess.call
|
||||
raise SystemExit(DNE)
|
||||
|
||||
CMD.extend(ARGS)
|
||||
if sys.platform == 'win32': # https://bugs.python.org/issue19124
|
||||
import subprocess
|
||||
|
||||
def main() -> int:
|
||||
retv, stdin = _run_legacy()
|
||||
try:
|
||||
_validate_config()
|
||||
return retv | _subprocess_call(_exe() + _opts(stdin))
|
||||
except EarlyExit:
|
||||
return retv
|
||||
except FatalError as e:
|
||||
print(e.args[0])
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
if sys.version_info < (3, 7): # https://bugs.python.org/issue25942
|
||||
raise SystemExit(subprocess.Popen(CMD).wait())
|
||||
else:
|
||||
raise SystemExit(subprocess.call(CMD))
|
||||
else:
|
||||
os.execvp(CMD[0], CMD)
|
||||
|
||||
Reference in New Issue
Block a user