-from functools import lru_cache
import io
import os
-from pathlib import Path
import sys
+from functools import lru_cache
+from pathlib import Path
from typing import (
+ TYPE_CHECKING,
Any,
Dict,
Iterable,
Sequence,
Tuple,
Union,
- TYPE_CHECKING,
)
from mypy_extensions import mypyc_attr
from pathspec.patterns.gitwildmatch import GitWildMatchPatternError
if sys.version_info >= (3, 11):
- import tomllib
+ try:
+ import tomllib
+ except ImportError:
+ # Help users on older alphas
+ if not TYPE_CHECKING:
+ import tomli as tomllib
else:
import tomli as tomllib
+from black.handle_ipynb_magics import jupyter_dependencies_are_installed
from black.output import err
from black.report import Report
-from black.handle_ipynb_magics import jupyter_dependencies_are_installed
if TYPE_CHECKING:
import colorama # noqa: F401
@lru_cache()
-def find_project_root(srcs: Sequence[str]) -> Tuple[Path, str]:
+def find_project_root(
+ srcs: Sequence[str], stdin_filename: Optional[str] = None
+) -> Tuple[Path, str]:
"""Return a directory containing .git, .hg, or pyproject.toml.
That directory will be a common parent of all files and directories
the second element as a string describing the method by which the
project root was discovered.
"""
+ if stdin_filename is not None:
+ srcs = tuple(stdin_filename if s == "-" else s for s in srcs)
if not srcs:
srcs = [str(Path.cwd().resolve())]
return root_relative_path
+def path_is_ignored(
+ path: Path, gitignore_dict: Dict[Path, PathSpec], report: Report
+) -> bool:
+ for gitignore_path, pattern in gitignore_dict.items():
+ relative_path = normalize_path_maybe_ignore(path, gitignore_path, report)
+ if relative_path is None:
+ break
+ if pattern.match_file(relative_path):
+ report.path_ignored(path, "matches a .gitignore file content")
+ return True
+ return False
+
+
def path_is_excluded(
normalized_path: str,
pattern: Optional[Pattern[str]],
extend_exclude: Optional[Pattern[str]],
force_exclude: Optional[Pattern[str]],
report: Report,
- gitignore: Optional[PathSpec],
+ gitignore_dict: Optional[Dict[Path, PathSpec]],
*,
verbose: bool,
quiet: bool,
`report` is where output about exclusions goes.
"""
+
assert root.is_absolute(), f"INTERNAL ERROR: `root` must be absolute but is {root}"
for child in paths:
normalized_path = normalize_path_maybe_ignore(child, root, report)
continue
# First ignore files matching .gitignore, if passed
- if gitignore is not None and gitignore.match_file(normalized_path):
- report.path_ignored(child, "matches the .gitignore file content")
+ if gitignore_dict and path_is_ignored(child, gitignore_dict, report):
continue
# Then ignore with `--exclude` `--extend-exclude` and `--force-exclude` options.
if child.is_dir():
# If gitignore is None, gitignore usage is disabled, while a Falsey
# gitignore is when the directory doesn't have a .gitignore file.
+ if gitignore_dict is not None:
+ new_gitignore_dict = {
+ **gitignore_dict,
+ root / child: get_gitignore(child),
+ }
+ else:
+ new_gitignore_dict = None
yield from gen_python_files(
child.iterdir(),
root,
extend_exclude,
force_exclude,
report,
- gitignore + get_gitignore(child) if gitignore is not None else None,
+ new_gitignore_dict,
verbose=verbose,
quiet=quiet,
)