diff --git a/CHANGES.md b/CHANGES.md index f2c795440f8..603554cd8b7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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_ diff --git a/docs/usage_and_configuration/file_collection_and_discovery.md b/docs/usage_and_configuration/file_collection_and_discovery.md index 54c76cd9a0f..1f436182dda 100644 --- a/docs/usage_and_configuration/file_collection_and_discovery.md +++ b/docs/usage_and_configuration/file_collection_and_discovery.md @@ -30,8 +30,7 @@ then write the above files to `.cache/black//`. ## .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`. diff --git a/src/black/files.py b/src/black/files.py index 1be560643a1..a0c92e8c415 100644 --- a/src/black/files.py +++ b/src/black/files.py @@ -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, @@ -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(): diff --git a/tests/data/nested_gitignore_tests/pyproject.toml b/tests/data/nested_gitignore_tests/pyproject.toml new file mode 100644 index 00000000000..9ba7ec26980 --- /dev/null +++ b/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" diff --git a/tests/data/nested_gitignore_tests/root/.gitignore b/tests/data/nested_gitignore_tests/root/.gitignore new file mode 100644 index 00000000000..2987e7bb646 --- /dev/null +++ b/tests/data/nested_gitignore_tests/root/.gitignore @@ -0,0 +1 @@ +a.py diff --git a/tests/data/nested_gitignore_tests/root/a.py b/tests/data/nested_gitignore_tests/root/a.py new file mode 100644 index 00000000000..7135cfd187c --- /dev/null +++ b/tests/data/nested_gitignore_tests/root/a.py @@ -0,0 +1 @@ +# should be excluded (root/.gitignore) diff --git a/tests/data/nested_gitignore_tests/root/b.py b/tests/data/nested_gitignore_tests/root/b.py new file mode 100644 index 00000000000..bdeeca3c602 --- /dev/null +++ b/tests/data/nested_gitignore_tests/root/b.py @@ -0,0 +1 @@ +# should be included diff --git a/tests/data/nested_gitignore_tests/root/c.py b/tests/data/nested_gitignore_tests/root/c.py new file mode 100644 index 00000000000..bdeeca3c602 --- /dev/null +++ b/tests/data/nested_gitignore_tests/root/c.py @@ -0,0 +1 @@ +# should be included diff --git a/tests/data/nested_gitignore_tests/root/child/.gitignore b/tests/data/nested_gitignore_tests/root/child/.gitignore new file mode 100644 index 00000000000..6df81dd798e --- /dev/null +++ b/tests/data/nested_gitignore_tests/root/child/.gitignore @@ -0,0 +1 @@ +b.py diff --git a/tests/data/nested_gitignore_tests/root/child/a.py b/tests/data/nested_gitignore_tests/root/child/a.py new file mode 100644 index 00000000000..7135cfd187c --- /dev/null +++ b/tests/data/nested_gitignore_tests/root/child/a.py @@ -0,0 +1 @@ +# should be excluded (root/.gitignore) diff --git a/tests/data/nested_gitignore_tests/root/child/b.py b/tests/data/nested_gitignore_tests/root/child/b.py new file mode 100644 index 00000000000..c91d47946e6 --- /dev/null +++ b/tests/data/nested_gitignore_tests/root/child/b.py @@ -0,0 +1 @@ +# should be excluded (child/.gitignore) diff --git a/tests/data/nested_gitignore_tests/root/child/c.py b/tests/data/nested_gitignore_tests/root/child/c.py new file mode 100644 index 00000000000..bdeeca3c602 --- /dev/null +++ b/tests/data/nested_gitignore_tests/root/child/c.py @@ -0,0 +1 @@ +# should be included diff --git a/tests/data/nested_gitignore_tests/x.py b/tests/data/nested_gitignore_tests/x.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_black.py b/tests/test_black.py index 5ab25cd1601..098a9ec9157 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -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/") @@ -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()