Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: black only respects the root gitignore. #2225

Merged
merged 10 commits into from May 16, 2021
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -4,6 +4,8 @@

### _Black_

- Respect `.gitignore` files in all levels, not only `root/.gitignore` file (apply
`.gitignore` rules like `git` does) (#2225)
- Restored compatibility with Click 8.0 on Python 3.6 when LANG=C used (#2227)

### _Blackd_
Expand Down
Expand Up @@ -30,8 +30,7 @@ then write the above files to `.cache/black/<version>/`.
## .gitignore

If `--exclude` is not set, _Black_ will automatically ignore files and directories in
`.gitignore` file, if present. The `.gitignore` file must be in the project root to be
used and nested `.gitignore` aren't supported.
`.gitignore` file(s), if present.

If you want _Black_ to continue using `.gitignore` while also configuring the exclusion
rules, please use `--extend-exclude`.
4 changes: 3 additions & 1 deletion src/black/files.py
Expand Up @@ -204,6 +204,8 @@ def gen_python_files(
continue

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.
yield from gen_python_files(
child.iterdir(),
root,
Expand All @@ -212,7 +214,7 @@ def gen_python_files(
extend_exclude,
force_exclude,
report,
gitignore,
gitignore + get_gitignore(child) if gitignore is not None else None,
)

elif child.is_file():
Expand Down
3 changes: 3 additions & 0 deletions tests/data/nested_gitignore_tests/pyproject.toml
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=41.0", "setuptools-scm", "wheel"]
build-backend = "setuptools.build_meta"
1 change: 1 addition & 0 deletions tests/data/nested_gitignore_tests/root/.gitignore
@@ -0,0 +1 @@
a.py
1 change: 1 addition & 0 deletions tests/data/nested_gitignore_tests/root/a.py
@@ -0,0 +1 @@
# should be excluded (root/.gitignore)
1 change: 1 addition & 0 deletions tests/data/nested_gitignore_tests/root/b.py
@@ -0,0 +1 @@
# should be included
1 change: 1 addition & 0 deletions tests/data/nested_gitignore_tests/root/c.py
@@ -0,0 +1 @@
# should be included
1 change: 1 addition & 0 deletions tests/data/nested_gitignore_tests/root/child/.gitignore
@@ -0,0 +1 @@
b.py
1 change: 1 addition & 0 deletions tests/data/nested_gitignore_tests/root/child/a.py
@@ -0,0 +1 @@
# should be excluded (root/.gitignore)
1 change: 1 addition & 0 deletions tests/data/nested_gitignore_tests/root/child/b.py
@@ -0,0 +1 @@
# should be excluded (child/.gitignore)
1 change: 1 addition & 0 deletions tests/data/nested_gitignore_tests/root/child/c.py
@@ -0,0 +1 @@
# should be included
Empty file.
29 changes: 28 additions & 1 deletion tests/test_black.py
Expand Up @@ -1406,7 +1406,7 @@ def test_include_exclude(self) -> None:
)
self.assertEqual(sorted(expected), sorted(sources))

def test_gitingore_used_as_default(self) -> None:
def test_gitignore_used_as_default(self) -> None:
path = Path(THIS_DIR / "data" / "include_exclude_tests")
include = re.compile(r"\.pyi?$")
extend_exclude = re.compile(r"/exclude/")
Expand Down Expand Up @@ -1703,6 +1703,33 @@ def test_gitignore_exclude(self) -> None:
)
self.assertEqual(sorted(expected), sorted(sources))

def test_nested_gitignore(self) -> None:
path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
include = re.compile(r"\.pyi?$")
exclude = re.compile(r"")
root_gitignore = black.files.get_gitignore(path)
report = black.Report()
expected: List[Path] = [
Path(path / "x.py"),
Path(path / "root/b.py"),
Path(path / "root/c.py"),
Path(path / "root/child/c.py"),
]
this_abs = THIS_DIR.resolve()
sources = list(
black.gen_python_files(
path.iterdir(),
this_abs,
include,
exclude,
None,
None,
report,
root_gitignore,
)
)
self.assertEqual(sorted(expected), sorted(sources))

def test_empty_include(self) -> None:
path = THIS_DIR / "data" / "include_exclude_tests"
report = black.Report()
Expand Down