diff --git a/CHANGES.md b/CHANGES.md index a678aaefc2a..dba493999ae 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ - Add support for formatting Jupyter Notebook files (#2357) - Move from `appdirs` dependency to `platformdirs` (#2375) +- Present a more user-friendly error if .gitignore is invalid (#2414) ## 21.7b0 diff --git a/setup.py b/setup.py index 92b78f1abe1..215fa6cff61 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def get_long_description() -> str: "tomli>=0.2.6,<2.0.0", "typed-ast>=1.4.2; python_version < '3.8'", "regex>=2020.1.8", - "pathspec>=0.8.1, <1", + "pathspec>=0.9.0, <1", "dataclasses>=0.6; python_version < '3.7'", "typing_extensions>=3.10.0.0; python_version < '3.10'", "mypy_extensions>=0.4.3", diff --git a/src/black/__init__.py b/src/black/__init__.py index 29fb244f8b7..056ade17425 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -495,6 +495,8 @@ def get_sources( if exclude is None: exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES) gitignore = get_gitignore(root) + if gitignore is None: + ctx.exit(1) else: gitignore = None diff --git a/src/black/files.py b/src/black/files.py index ba60c84a275..34e24f708f2 100644 --- a/src/black/files.py +++ b/src/black/files.py @@ -18,6 +18,7 @@ ) from pathspec import PathSpec +from pathspec.patterns.gitwildmatch import GitWildMatchPatternError import tomli from black.output import err @@ -122,7 +123,11 @@ def get_gitignore(root: Path) -> PathSpec: if gitignore.is_file(): with gitignore.open(encoding="utf-8") as gf: lines = gf.readlines() - return PathSpec.from_lines("gitwildmatch", lines) + try: + return PathSpec.from_lines("gitwildmatch", lines) + except GitWildMatchPatternError as e: + err(f"Could not parse {gitignore}: {e}") + return None def normalize_path_maybe_ignore( diff --git a/tests/data/invalid_gitignore_tests/.gitignore b/tests/data/invalid_gitignore_tests/.gitignore new file mode 100644 index 00000000000..cdf4cb4feba --- /dev/null +++ b/tests/data/invalid_gitignore_tests/.gitignore @@ -0,0 +1 @@ +! diff --git a/tests/data/invalid_gitignore_tests/a.py b/tests/data/invalid_gitignore_tests/a.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/data/invalid_gitignore_tests/pyproject.toml b/tests/data/invalid_gitignore_tests/pyproject.toml new file mode 100644 index 00000000000..3908e457a9e --- /dev/null +++ b/tests/data/invalid_gitignore_tests/pyproject.toml @@ -0,0 +1 @@ +# Empty configuration file; used in tests to avoid interference from Black's own config. diff --git a/tests/test_black.py b/tests/test_black.py index 942446ec343..ad1ddc4f9f3 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -1727,6 +1727,18 @@ def test_nested_gitignore(self) -> None: ) self.assertEqual(sorted(expected), sorted(sources)) + def test_invalid_gitignore(self) -> None: + path = THIS_DIR / "data" / "invalid_gitignore_tests" + empty_config = THIS_DIR / "data" / "invalid_gitignore_tests" / "pyproject.toml" + result = BlackRunner().invoke( + black.main, ["--verbose", "--config", str(empty_config), str(path)] + ) + assert result.exit_code == 1 + assert result.stderr_bytes is not None + + gitignore = path / ".gitignore" + assert f"Could not parse {gitignore}" in result.stderr_bytes.decode() + def test_empty_include(self) -> None: path = THIS_DIR / "data" / "include_exclude_tests" report = black.Report()