From 104aec555fae0883ef5b53709569bd9c4d420bc5 Mon Sep 17 00:00:00 2001 From: Nipunn Koorapati Date: Fri, 20 Aug 2021 16:54:53 -0700 Subject: [PATCH] Present a more user-friendly error if .gitignore is invalid (#2414) Fixes #2359. This commit now makes Black exit with an user-friendly error message if a .gitignore file couldn't be parsed -- a massive improvement over an opaque traceback! --- CHANGES.md | 1 + setup.py | 2 +- src/black/__init__.py | 28 +++++++++++-------- src/black/files.py | 7 ++++- tests/data/invalid_gitignore_tests/.gitignore | 1 + tests/data/invalid_gitignore_tests/a.py | 0 .../invalid_gitignore_tests/pyproject.toml | 1 + .../data/invalid_nested_gitignore_tests/a.py | 0 .../a/.gitignore | 1 + .../invalid_nested_gitignore_tests/a/a.py | 0 .../pyproject.toml | 1 + tests/test_black.py | 24 ++++++++++++++++ 12 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 tests/data/invalid_gitignore_tests/.gitignore create mode 100644 tests/data/invalid_gitignore_tests/a.py create mode 100644 tests/data/invalid_gitignore_tests/pyproject.toml create mode 100644 tests/data/invalid_nested_gitignore_tests/a.py create mode 100644 tests/data/invalid_nested_gitignore_tests/a/.gitignore create mode 100644 tests/data/invalid_nested_gitignore_tests/a/a.py create mode 100644 tests/data/invalid_nested_gitignore_tests/pyproject.toml diff --git a/CHANGES.md b/CHANGES.md index a4b8e01..3a96029 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) ### Integrations diff --git a/setup.py b/setup.py index 92b78f1..215fa6c 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ setup( "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 29fb244..60f4fa3 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -9,6 +9,7 @@ import io from multiprocessing import Manager, freeze_support import os from pathlib import Path +from pathspec.patterns.gitwildmatch import GitWildMatchPatternError import regex as re import signal import sys @@ -428,18 +429,21 @@ def main( content=code, fast=fast, write_back=write_back, mode=mode, report=report ) else: - sources = get_sources( - ctx=ctx, - src=src, - quiet=quiet, - verbose=verbose, - include=include, - exclude=exclude, - extend_exclude=extend_exclude, - force_exclude=force_exclude, - report=report, - stdin_filename=stdin_filename, - ) + try: + sources = get_sources( + ctx=ctx, + src=src, + quiet=quiet, + verbose=verbose, + include=include, + exclude=exclude, + extend_exclude=extend_exclude, + force_exclude=force_exclude, + report=report, + stdin_filename=stdin_filename, + ) + except GitWildMatchPatternError: + ctx.exit(1) path_empty( sources, diff --git a/src/black/files.py b/src/black/files.py index ba60c84..4d7b47a 100644 --- a/src/black/files.py +++ b/src/black/files.py @@ -18,6 +18,7 @@ from typing import ( ) 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}") + raise 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 0000000..cdf4cb4 --- /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 0000000..e69de29 diff --git a/tests/data/invalid_gitignore_tests/pyproject.toml b/tests/data/invalid_gitignore_tests/pyproject.toml new file mode 100644 index 0000000..3908e45 --- /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/data/invalid_nested_gitignore_tests/a.py b/tests/data/invalid_nested_gitignore_tests/a.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/invalid_nested_gitignore_tests/a/.gitignore b/tests/data/invalid_nested_gitignore_tests/a/.gitignore new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/data/invalid_nested_gitignore_tests/a/.gitignore @@ -0,0 +1 @@ +! diff --git a/tests/data/invalid_nested_gitignore_tests/a/a.py b/tests/data/invalid_nested_gitignore_tests/a/a.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/invalid_nested_gitignore_tests/pyproject.toml b/tests/data/invalid_nested_gitignore_tests/pyproject.toml new file mode 100644 index 0000000..3908e45 --- /dev/null +++ b/tests/data/invalid_nested_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 942446e..5c72050 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -1727,6 +1727,30 @@ class BlackTestCase(BlackBaseTestCase): ) self.assertEqual(sorted(expected), sorted(sources)) + def test_invalid_gitignore(self) -> None: + path = THIS_DIR / "data" / "invalid_gitignore_tests" + empty_config = path / "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_invalid_nested_gitignore(self) -> None: + path = THIS_DIR / "data" / "invalid_nested_gitignore_tests" + empty_config = path / "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 / "a" / ".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() -- 2.39.5