mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-04-16 02:21:46 +04:00
Save conda environment that was active during conda install when using option --hooks-activate-conda. The saved environment will be activated before calling pre-commit hooks. Especially on Windows, more and more actions within a conda environment require the conda environment to be activated. Thus saving just the python executable is not enough any more. There is currently one downside of using the option --hooks-activate-conda. It uses "conda run" which will only show console output after the run is completed. We have a pull request to conda open which introduces an option to show interactive console output in conda run. Once this is approved, it might be ok to make this option the default behaviour.
186 lines
5.9 KiB
Python
186 lines
5.9 KiB
Python
import itertools
|
|
import logging
|
|
import os.path
|
|
import shutil
|
|
import sys
|
|
from typing import Optional
|
|
from typing import Sequence
|
|
from typing import Tuple
|
|
|
|
from pre_commit import git
|
|
from pre_commit import output
|
|
from pre_commit.clientlib import load_config
|
|
from pre_commit.repository import all_hooks
|
|
from pre_commit.repository import install_hook_envs
|
|
from pre_commit.store import Store
|
|
from pre_commit.util import make_executable
|
|
from pre_commit.util import resource_text
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# This is used to identify the hook file we install
|
|
PRIOR_HASHES = (
|
|
'4d9958c90bc262f47553e2c073f14cfe',
|
|
'd8ee923c46731b42cd95cc869add4062',
|
|
'49fd668cb42069aa1b6048464be5d395',
|
|
'79f09a650522a87b0da915d0d983b2de',
|
|
'e358c9dae00eac5d06b38dfdb1e33a8c',
|
|
)
|
|
CURRENT_HASH = '138fd403232d2ddd5efb44317e38bf03'
|
|
TEMPLATE_START = '# start templated\n'
|
|
TEMPLATE_END = '# end templated\n'
|
|
|
|
|
|
def _hook_paths(
|
|
hook_type: str,
|
|
git_dir: Optional[str] = None,
|
|
) -> Tuple[str, str]:
|
|
git_dir = git_dir if git_dir is not None else git.get_git_dir()
|
|
pth = os.path.join(git_dir, 'hooks', hook_type)
|
|
return pth, f'{pth}.legacy'
|
|
|
|
|
|
def is_our_script(filename: str) -> bool:
|
|
if not os.path.exists(filename): # pragma: windows no cover (symlink)
|
|
return False
|
|
with open(filename) as f:
|
|
contents = f.read()
|
|
return any(h in contents for h in (CURRENT_HASH,) + PRIOR_HASHES)
|
|
|
|
|
|
def shebang() -> str:
|
|
if sys.platform == 'win32':
|
|
py = 'python'
|
|
else:
|
|
# Homebrew/homebrew-core#35825: be more timid about appropriate `PATH`
|
|
path_choices = [p for p in os.defpath.split(os.pathsep) if p]
|
|
exe_choices = [
|
|
f'python{sys.version_info[0]}.{sys.version_info[1]}',
|
|
f'python{sys.version_info[0]}',
|
|
]
|
|
for path, exe in itertools.product(path_choices, exe_choices):
|
|
if os.access(os.path.join(path, exe), os.X_OK):
|
|
py = exe
|
|
break
|
|
else:
|
|
py = 'python'
|
|
return f'#!/usr/bin/env {py}'
|
|
|
|
|
|
def _install_hook_script(
|
|
config_file: str,
|
|
hook_type: str,
|
|
overwrite: bool = False,
|
|
skip_on_missing_config: bool = False,
|
|
hooks_activate_conda: bool = False,
|
|
git_dir: Optional[str] = None,
|
|
) -> None:
|
|
hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir)
|
|
|
|
os.makedirs(os.path.dirname(hook_path), exist_ok=True)
|
|
|
|
# If we have an existing hook, move it to pre-commit.legacy
|
|
if os.path.lexists(hook_path) and not is_our_script(hook_path):
|
|
shutil.move(hook_path, legacy_path)
|
|
|
|
# If we specify overwrite, we simply delete the legacy file
|
|
if overwrite and os.path.exists(legacy_path):
|
|
os.remove(legacy_path)
|
|
elif os.path.exists(legacy_path):
|
|
output.write_line(
|
|
f'Running in migration mode with existing hooks at {legacy_path}\n'
|
|
f'Use -f to use only pre-commit.',
|
|
)
|
|
|
|
args = ['hook-impl', f'--config={config_file}', f'--hook-type={hook_type}']
|
|
if skip_on_missing_config:
|
|
args.append('--skip-on-missing-config')
|
|
params = {
|
|
'INSTALL_PYTHON': sys.executable, 'INSTALL_CONDA': '',
|
|
'INSTALL_CONDA_PREFIX': '', 'ARGS': args,
|
|
}
|
|
if hooks_activate_conda:
|
|
if 'CONDA_EXE' in os.environ and 'CONDA_PREFIX' in os.environ:
|
|
params['INSTALL_PYTHON'] = '' # conda will find correct python
|
|
params['INSTALL_CONDA'] = os.getenv('CONDA_EXE', '')
|
|
params['INSTALL_CONDA_PREFIX'] = os.getenv('CONDA_PREFIX', '')
|
|
else:
|
|
logger.warning(
|
|
'Failed to detect activated conda, '
|
|
'ignoring option --hooks_activate_conda.',
|
|
)
|
|
|
|
with open(hook_path, 'w') as hook_file:
|
|
contents = resource_text('hook-tmpl')
|
|
before, rest = contents.split(TEMPLATE_START)
|
|
to_template, after = rest.split(TEMPLATE_END)
|
|
|
|
before = before.replace('#!/usr/bin/env python3', shebang())
|
|
|
|
hook_file.write(before + TEMPLATE_START)
|
|
for line in to_template.splitlines():
|
|
var = line.split()[0]
|
|
hook_file.write(f'{var} = {params[var]!r}\n')
|
|
hook_file.write(TEMPLATE_END + after)
|
|
make_executable(hook_path)
|
|
|
|
output.write_line(f'pre-commit installed at {hook_path}')
|
|
|
|
|
|
def install(
|
|
config_file: str,
|
|
store: Store,
|
|
hook_types: Sequence[str],
|
|
overwrite: bool = False,
|
|
hooks: bool = False,
|
|
skip_on_missing_config: bool = False,
|
|
hooks_activate_conda: bool = False,
|
|
git_dir: Optional[str] = None,
|
|
) -> int:
|
|
if git_dir is None and git.has_core_hookpaths_set():
|
|
logger.error(
|
|
'Cowardly refusing to install hooks with `core.hooksPath` set.\n'
|
|
'hint: `git config --unset-all core.hooksPath`',
|
|
)
|
|
return 1
|
|
|
|
for hook_type in hook_types:
|
|
_install_hook_script(
|
|
config_file, hook_type,
|
|
overwrite=overwrite,
|
|
skip_on_missing_config=skip_on_missing_config,
|
|
hooks_activate_conda=hooks_activate_conda,
|
|
git_dir=git_dir,
|
|
)
|
|
|
|
if hooks:
|
|
install_hooks(config_file, store)
|
|
|
|
return 0
|
|
|
|
|
|
def install_hooks(config_file: str, store: Store) -> int:
|
|
install_hook_envs(all_hooks(load_config(config_file), store), store)
|
|
return 0
|
|
|
|
|
|
def _uninstall_hook_script(hook_type: str) -> None:
|
|
hook_path, legacy_path = _hook_paths(hook_type)
|
|
|
|
# If our file doesn't exist or it isn't ours, gtfo.
|
|
if not os.path.exists(hook_path) or not is_our_script(hook_path):
|
|
return
|
|
|
|
os.remove(hook_path)
|
|
output.write_line(f'{hook_type} uninstalled')
|
|
|
|
if os.path.exists(legacy_path):
|
|
os.rename(legacy_path, hook_path)
|
|
output.write_line(f'Restored previous hooks to {hook_path}')
|
|
|
|
|
|
def uninstall(hook_types: Sequence[str]) -> int:
|
|
for hook_type in hook_types:
|
|
_uninstall_hook_script(hook_type)
|
|
return 0
|