mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-04-15 10:01:46 +04:00
feature: add monorepo recursive configuration
Finds all ".pre-commit-config.yaml" files recursively, depending on the staged files. Only tested on Linux.
This commit is contained in:
parent
bb49560dc9
commit
d8295a246b
1 changed files with 58 additions and 23 deletions
|
|
@ -9,7 +9,7 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from typing import Any
|
from typing import Any, List
|
||||||
from typing import Collection
|
from typing import Collection
|
||||||
from typing import MutableMapping
|
from typing import MutableMapping
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
@ -317,7 +317,6 @@ def _run_hooks(
|
||||||
'git', '--no-pager', 'diff', '--no-ext-diff',
|
'git', '--no-pager', 'diff', '--no-ext-diff',
|
||||||
f'--color={git_color_opt}',
|
f'--color={git_color_opt}',
|
||||||
))
|
))
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -334,6 +333,36 @@ def _has_unstaged_config(config_file: str) -> bool:
|
||||||
return retcode == 1
|
return retcode == 1
|
||||||
|
|
||||||
|
|
||||||
|
def _dive_into_file_hierarchy(dir_path: str):
|
||||||
|
""" Iterator for crawling recursively a path.
|
||||||
|
:note!: Not tested on windows (hardcoded '/' for path separator).
|
||||||
|
"""
|
||||||
|
accumulated = "" # start from empty string
|
||||||
|
_split = dir_path.split('/')
|
||||||
|
while len(_split) > 0:
|
||||||
|
accumulated = os.path.join(accumulated, _split.pop(0))
|
||||||
|
yield accumulated
|
||||||
|
|
||||||
|
def _find_all_config_files(modified_files: List[str], config_file_name: str) -> List[str]:
|
||||||
|
""" Finds all the config files relative to modified files.
|
||||||
|
Every modified file can have a :config_file_name: in its parent directory. If found, get it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ret_set = set()
|
||||||
|
for file_name in modified_files:
|
||||||
|
directory_base = os.path.dirname(file_name)
|
||||||
|
for subdir_seq_item in _dive_into_file_hierarchy(directory_base):
|
||||||
|
candidate_fileconfig = os.path.join(subdir_seq_item, config_file_name)
|
||||||
|
if os.path.exists(candidate_fileconfig):
|
||||||
|
# logging.debug(f"Will use config file: {candidate_fileconfig}")
|
||||||
|
ret_set.add(candidate_fileconfig)
|
||||||
|
# do not forget the root dir !
|
||||||
|
if os.path.exists(config_file_name):
|
||||||
|
# logging.debug(f"Will use config file: {config_file_name}")
|
||||||
|
ret_set.add(config_file_name)
|
||||||
|
ret = sorted(list(ret_set))
|
||||||
|
return ret
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
config_file: str,
|
config_file: str,
|
||||||
store: Store,
|
store: Store,
|
||||||
|
|
@ -341,7 +370,6 @@ def run(
|
||||||
environ: MutableMapping[str, str] = os.environ,
|
environ: MutableMapping[str, str] = os.environ,
|
||||||
) -> int:
|
) -> int:
|
||||||
stash = not args.all_files and not args.files
|
stash = not args.all_files and not args.files
|
||||||
|
|
||||||
# Check if we have unresolved merge conflict files and fail fast.
|
# Check if we have unresolved merge conflict files and fail fast.
|
||||||
if stash and _has_unmerged_paths():
|
if stash and _has_unmerged_paths():
|
||||||
logger.error('Unmerged files. Resolve before committing.')
|
logger.error('Unmerged files. Resolve before committing.')
|
||||||
|
|
@ -419,7 +447,13 @@ def run(
|
||||||
if stash:
|
if stash:
|
||||||
exit_stack.enter_context(staged_files_only(store.directory))
|
exit_stack.enter_context(staged_files_only(store.directory))
|
||||||
|
|
||||||
config = load_config(config_file)
|
ret_int = 0
|
||||||
|
# find all applicable config files
|
||||||
|
all_config_files = _find_all_config_files(_all_filenames(args), config_file)
|
||||||
|
for _config_file in all_config_files:
|
||||||
|
config = load_config(_config_file)
|
||||||
|
if len(all_config_files) > 0 :
|
||||||
|
print(f"Hooks from {_config_file}:")
|
||||||
hooks = [
|
hooks = [
|
||||||
hook
|
hook
|
||||||
for hook in all_hooks(config, store)
|
for hook in all_hooks(config, store)
|
||||||
|
|
@ -429,7 +463,7 @@ def run(
|
||||||
|
|
||||||
if args.hook and not hooks:
|
if args.hook and not hooks:
|
||||||
output.write_line(
|
output.write_line(
|
||||||
f'No hook with id `{args.hook}` in stage `{args.hook_stage}`',
|
f'No hook with id `{args.hook}` in stage `{args.hook_stage}` of file {_config_file}',
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
@ -441,7 +475,8 @@ def run(
|
||||||
]
|
]
|
||||||
install_hook_envs(to_install, store)
|
install_hook_envs(to_install, store)
|
||||||
|
|
||||||
return _run_hooks(config, hooks, skips, args)
|
ret_int += _run_hooks(config, hooks, skips, args) # accumulate errors
|
||||||
|
return ret_int
|
||||||
|
|
||||||
# https://github.com/python/mypy/issues/7726
|
# https://github.com/python/mypy/issues/7726
|
||||||
raise AssertionError('unreachable')
|
raise AssertionError('unreachable')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue