Skip to content

Commit

Permalink
Read exclude patterns from .gitignore in absence of user-provided pat…
Browse files Browse the repository at this point in the history
…terns (#344) (#345)

* Read exclude patterns from .gitignore in absence of user-provided
patterns (#344)

Use .gitignore to exclude files if --exclude is missing from both
pyproject.toml and the command line. Vulture now requires the
pathspec library to run.

* Move dependencies to requirements.txt.

---------

Co-authored-by: Jendrik Seipp <[email protected]>
  • Loading branch information
whosayn and jendrikseipp authored Dec 23, 2023
1 parent 3d0ad1a commit aa6fcd2
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 2 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade build coveralls setuptools tox wheel
python -m pip install -r requirements.txt
- name: Build Vulture wheel
run: python -m build
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Bump flake8, flake8-comprehensions and flake8-bugbear (Sebastian Csar, #341).
* Switch to tomllib/tomli to support heterogeneous arrays (Sebastian Csar, #340).
* Provide whitelist parity for `MagicMock` and `Mock` (maxrake).
* Use .gitignore to exclude files if --exclude is missing from both pyproject.toml and the command line (whosayn, #344, #345).

# 2.10 (2023-10-06)

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ If you want to ignore a whole file or directory, use the `--exclude` parameter
(e.g., `--exclude "*settings.py,*/docs/*.py,*/test_*.py,*/.venv/*.py"`). The
exclude patterns are matched against absolute paths.

Vulture 2.11+ parses the `.gitignore` file in the current working directory for
exclude patterns if the `--exclude` parameter is unused and if there are no
exclude patterns in the pyproject.toml file.

#### Flake8 noqa comments

<!-- Hide noqa docs until we decide whether we want to support it.
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pathspec >= 0.12.1
tomli >= 1.1.0; python_version < '3.11'
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def find_version(*parts):
with open("README.md") as f1, open("CHANGELOG.md") as f2:
long_description = f1.read() + "\n\n" + f2.read()

with open("requirements.txt") as f:
install_requires = f.read().splitlines()

setuptools.setup(
name="vulture",
version=find_version("vulture", "version.py"),
Expand Down Expand Up @@ -47,7 +50,7 @@ def find_version(*parts):
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Quality Assurance",
],
install_requires=["tomli >= 1.1.0; python_version < '3.11'"],
install_requires=install_requires,
entry_points={"console_scripts": ["vulture = vulture.core:main"]},
python_requires=">=3.8",
packages=setuptools.find_packages(exclude=["tests"]),
Expand Down
56 changes: 56 additions & 0 deletions tests/test_gitignore_patterns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import pathlib
import pytest
import shutil

from . import call_vulture
from vulture.utils import ExitCode


class TempGitignore:
def __init__(self, patterns):
self.patterns = patterns
root = pathlib.Path(".").resolve()
self.file = root / ".gitignore"
self.tmpfile = root / ".tmp_gitignore"

def __enter__(self):
shutil.move(self.file, self.tmpfile)
with open(self.file, "w") as f:
f.write("\n".join(self.patterns))

return self.file

def __exit__(self, *args):
os.remove(self.file)
shutil.move(self.tmpfile, self.file)


@pytest.fixture(scope="function")
def gitignore(request):
with TempGitignore(request.param) as fpath:
yield fpath


@pytest.mark.parametrize(
"exclude_patterns,gitignore,exit_code",
(
([], [], ExitCode.NoDeadCode),
([""], [], ExitCode.NoDeadCode),
([], [""], ExitCode.NoDeadCode),
([""], ["core.py", "utils.py"], ExitCode.NoDeadCode),
(["core.py", "utils.py"], [""], ExitCode.DeadCode),
([], ["core.py", "utils.py"], ExitCode.DeadCode),
),
indirect=("gitignore",),
)
def test_gitignore(exclude_patterns, gitignore, exit_code):
def get_csv(paths):
return ",".join(os.path.join("vulture", path) for path in paths)

cli_args = ["vulture/"]
if exclude_patterns:
cli_args.extend(["--exclude", get_csv(exclude_patterns)])

assert gitignore.is_file()
assert call_vulture(cli_args) == exit_code
17 changes: 16 additions & 1 deletion vulture/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import string
import sys

from pathspec import PathSpec
from vulture import lines
from vulture import noqa
from vulture import utils
Expand Down Expand Up @@ -114,6 +115,13 @@ def _ignore_variable(filename, varname):
)


def _get_gitignore_pathspec():
if (gitignore := Path(".gitignore").resolve()).is_file:
with gitignore.open() as fh:
return PathSpec.from_lines("gitwildmatch", fh)
return PathSpec.from_lines("gitwildmatch", [])


class Item:
"""
Hold the name, type and location of defined code.
Expand Down Expand Up @@ -263,9 +271,16 @@ def prepare_pattern(pattern):
return pattern

exclude = [prepare_pattern(pattern) for pattern in (exclude or [])]
gitignore = _get_gitignore_pathspec()

def exclude_path(path):
return _match(path, exclude, case=False)
# If no exclude patterns are provided via the CLI or
# a TOML file, use .gitignore patterns to inform exclusion.
return (
_match(path, exclude, case=False)
if exclude
else gitignore.match_file(path)
)

paths = [Path(path) for path in paths]

Expand Down

0 comments on commit aa6fcd2

Please sign in to comment.