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

Present a more user-friendly error if .gitignore is invalid #2414

Merged
merged 1 commit into from Aug 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -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",
Expand Down
28 changes: 16 additions & 12 deletions src/black/__init__.py
Expand Up @@ -9,6 +9,7 @@
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
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion src/black/files.py
Expand Up @@ -18,6 +18,7 @@
)

from pathspec import PathSpec
from pathspec.patterns.gitwildmatch import GitWildMatchPatternError
import tomli

from black.output import err
Expand Down Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions tests/data/invalid_gitignore_tests/.gitignore
@@ -0,0 +1 @@
!
Empty file.
1 change: 1 addition & 0 deletions tests/data/invalid_gitignore_tests/pyproject.toml
@@ -0,0 +1 @@
# Empty configuration file; used in tests to avoid interference from Black's own config.
Empty file.
1 change: 1 addition & 0 deletions tests/data/invalid_nested_gitignore_tests/a/.gitignore
@@ -0,0 +1 @@
!
Empty file.
1 change: 1 addition & 0 deletions 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.
24 changes: 24 additions & 0 deletions tests/test_black.py
Expand Up @@ -1727,6 +1727,30 @@ 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 = 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()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here - it only checks for the presence of black's own error message


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()
Expand Down