Do not use gitignore if explicitly passing excludes (#2170)

Closes #2164.

Changes behavior of how .gitignore is handled. With this change, the rules in .gitignore are only used as a fallback if no exclusion rule is explicitly passed on the command line or in pyproject.toml. Previously they were used regardless if explicit exclusion rules were specified, preventing any overriding of .gitignore rules.

Those that depend only on .gitignore for their exclusion rules will not be affected. Those that use both .gitignore and exclude will find that exclude will act more like actually specifying exclude and not just another extra-excludes. If the previous behavior was desired, they should move their rules from exclude to extra-excludes.
This commit is contained in:
Kaleb Barrett 2021-05-07 07:54:21 -05:00 committed by GitHub
parent 4b7b5ed5b8
commit 1fe2efd857
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 50 additions and 10 deletions

View File

@ -22,6 +22,9 @@
[circumstances](https://github.com/psf/black/blob/master/docs/the_black_code_style.md#pragmatism)
in which _Black_ may change the AST (#2159)
- Allow `.gitignore` rules to be overridden by specifying `exclude` in `pyproject.toml`
or on the command line. (#2170)
#### _Packaging_
- Install `primer.json` (used by `black-primer` by default) with black. (#2154)

View File

@ -492,15 +492,15 @@ def validate_regex(
@click.option(
"--exclude",
type=str,
default=DEFAULT_EXCLUDES,
callback=validate_regex,
help=(
"A regular expression that matches files and directories that should be"
" excluded on recursive searches. An empty value means no paths are excluded."
" Use forward slashes for directories on all platforms (Windows, too)."
" Exclusions are calculated first, inclusions later."
" Exclusions are calculated first, inclusions later. [default:"
f" {DEFAULT_EXCLUDES}]"
),
show_default=True,
show_default=False,
)
@click.option(
"--extend-exclude",
@ -587,7 +587,7 @@ def main(
quiet: bool,
verbose: bool,
include: Pattern,
exclude: Pattern,
exclude: Optional[Pattern],
extend_exclude: Optional[Pattern],
force_exclude: Optional[Pattern],
stdin_filename: Optional[str],
@ -662,7 +662,7 @@ def get_sources(
quiet: bool,
verbose: bool,
include: Pattern[str],
exclude: Pattern[str],
exclude: Optional[Pattern[str]],
extend_exclude: Optional[Pattern[str]],
force_exclude: Optional[Pattern[str]],
report: "Report",
@ -673,7 +673,12 @@ def get_sources(
root = find_project_root(src)
sources: Set[Path] = set()
path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)
if exclude is None:
exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
gitignore = get_gitignore(root)
else:
gitignore = None
for s in src:
if s == "-" and stdin_filename:
@ -6215,12 +6220,12 @@ def path_is_excluded(
def gen_python_files(
paths: Iterable[Path],
root: Path,
include: Optional[Pattern[str]],
include: Pattern[str],
exclude: Pattern[str],
extend_exclude: Optional[Pattern[str]],
force_exclude: Optional[Pattern[str]],
report: "Report",
gitignore: PathSpec,
gitignore: Optional[PathSpec],
) -> Iterator[Path]:
"""Generate all files under `path` whose paths are not excluded by the
`exclude_regex`, `extend_exclude`, or `force_exclude` regexes,
@ -6236,8 +6241,8 @@ def gen_python_files(
if normalized_path is None:
continue
# First ignore files matching .gitignore
if gitignore.match_file(normalized_path):
# 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")
continue

View File

@ -0,0 +1 @@
dont_exclude/

View File

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=41.0", "setuptools-scm", "wheel"]
build-backend = "setuptools.build_meta"

View File

@ -1418,6 +1418,32 @@ def test_include_exclude(self) -> None:
)
self.assertEqual(sorted(expected), sorted(sources))
def test_gitingore_used_as_default(self) -> None:
path = Path(THIS_DIR / "data" / "include_exclude_tests")
include = re.compile(r"\.pyi?$")
extend_exclude = re.compile(r"/exclude/")
src = str(path / "b/")
report = black.Report()
expected: List[Path] = [
path / "b/.definitely_exclude/a.py",
path / "b/.definitely_exclude/a.pyi",
]
sources = list(
black.get_sources(
ctx=FakeContext(),
src=(src,),
quiet=True,
verbose=False,
include=include,
exclude=None,
extend_exclude=extend_exclude,
force_exclude=None,
report=report,
stdin_filename=None,
)
)
self.assertEqual(sorted(expected), sorted(sources))
@patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
def test_exclude_for_issue_1572(self) -> None:
# Exclude shouldn't touch files that were explicitly given to Black through the
@ -1705,6 +1731,8 @@ def test_empty_include(self) -> None:
Path(path / "b/.definitely_exclude/a.pie"),
Path(path / "b/.definitely_exclude/a.py"),
Path(path / "b/.definitely_exclude/a.pyi"),
Path(path / ".gitignore"),
Path(path / "pyproject.toml"),
]
this_abs = THIS_DIR.resolve()
sources.extend(