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!
This commit is contained in:
Nipunn Koorapati 2021-08-20 16:54:53 -07:00 committed by GitHub
parent ef7c45f281
commit 104aec555f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 52 additions and 14 deletions

View File

@ -6,6 +6,7 @@
- Add support for formatting Jupyter Notebook files (#2357) - Add support for formatting Jupyter Notebook files (#2357)
- Move from `appdirs` dependency to `platformdirs` (#2375) - Move from `appdirs` dependency to `platformdirs` (#2375)
- Present a more user-friendly error if .gitignore is invalid (#2414)
### Integrations ### Integrations

View File

@ -77,7 +77,7 @@ def get_long_description() -> str:
"tomli>=0.2.6,<2.0.0", "tomli>=0.2.6,<2.0.0",
"typed-ast>=1.4.2; python_version < '3.8'", "typed-ast>=1.4.2; python_version < '3.8'",
"regex>=2020.1.8", "regex>=2020.1.8",
"pathspec>=0.8.1, <1", "pathspec>=0.9.0, <1",
"dataclasses>=0.6; python_version < '3.7'", "dataclasses>=0.6; python_version < '3.7'",
"typing_extensions>=3.10.0.0; python_version < '3.10'", "typing_extensions>=3.10.0.0; python_version < '3.10'",
"mypy_extensions>=0.4.3", "mypy_extensions>=0.4.3",

View File

@ -9,6 +9,7 @@
from multiprocessing import Manager, freeze_support from multiprocessing import Manager, freeze_support
import os import os
from pathlib import Path from pathlib import Path
from pathspec.patterns.gitwildmatch import GitWildMatchPatternError
import regex as re import regex as re
import signal import signal
import sys import sys
@ -428,18 +429,21 @@ def main(
content=code, fast=fast, write_back=write_back, mode=mode, report=report content=code, fast=fast, write_back=write_back, mode=mode, report=report
) )
else: else:
sources = get_sources( try:
ctx=ctx, sources = get_sources(
src=src, ctx=ctx,
quiet=quiet, src=src,
verbose=verbose, quiet=quiet,
include=include, verbose=verbose,
exclude=exclude, include=include,
extend_exclude=extend_exclude, exclude=exclude,
force_exclude=force_exclude, extend_exclude=extend_exclude,
report=report, force_exclude=force_exclude,
stdin_filename=stdin_filename, report=report,
) stdin_filename=stdin_filename,
)
except GitWildMatchPatternError:
ctx.exit(1)
path_empty( path_empty(
sources, sources,

View File

@ -18,6 +18,7 @@
) )
from pathspec import PathSpec from pathspec import PathSpec
from pathspec.patterns.gitwildmatch import GitWildMatchPatternError
import tomli import tomli
from black.output import err from black.output import err
@ -122,7 +123,11 @@ def get_gitignore(root: Path) -> PathSpec:
if gitignore.is_file(): if gitignore.is_file():
with gitignore.open(encoding="utf-8") as gf: with gitignore.open(encoding="utf-8") as gf:
lines = gf.readlines() 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( def normalize_path_maybe_ignore(

View File

@ -0,0 +1 @@
!

View File

View File

@ -0,0 +1 @@
# Empty configuration file; used in tests to avoid interference from Black's own config.

View File

@ -0,0 +1 @@
!

View File

@ -0,0 +1 @@
# Empty configuration file; used in tests to avoid interference from Black's own config.

View File

@ -1727,6 +1727,30 @@ def test_nested_gitignore(self) -> None:
) )
self.assertEqual(sorted(expected), sorted(sources)) 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: def test_empty_include(self) -> None:
path = THIS_DIR / "data" / "include_exclude_tests" path = THIS_DIR / "data" / "include_exclude_tests"
report = black.Report() report = black.Report()