Skip to content

Commit aa6fcd2

Browse files
Read exclude patterns from .gitignore in absence of user-provided patterns (#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]>
1 parent 3d0ad1a commit aa6fcd2

File tree

7 files changed

+84
-2
lines changed

7 files changed

+84
-2
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131
run: |
3232
python -m pip install --upgrade pip
3333
python -m pip install --upgrade build coveralls setuptools tox wheel
34+
python -m pip install -r requirements.txt
3435
3536
- name: Build Vulture wheel
3637
run: python -m build

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Bump flake8, flake8-comprehensions and flake8-bugbear (Sebastian Csar, #341).
33
* Switch to tomllib/tomli to support heterogeneous arrays (Sebastian Csar, #340).
44
* Provide whitelist parity for `MagicMock` and `Mock` (maxrake).
5+
* Use .gitignore to exclude files if --exclude is missing from both pyproject.toml and the command line (whosayn, #344, #345).
56

67
# 2.10 (2023-10-06)
78

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ If you want to ignore a whole file or directory, use the `--exclude` parameter
8383
(e.g., `--exclude "*settings.py,*/docs/*.py,*/test_*.py,*/.venv/*.py"`). The
8484
exclude patterns are matched against absolute paths.
8585

86+
Vulture 2.11+ parses the `.gitignore` file in the current working directory for
87+
exclude patterns if the `--exclude` parameter is unused and if there are no
88+
exclude patterns in the pyproject.toml file.
89+
8690
#### Flake8 noqa comments
8791

8892
<!-- Hide noqa docs until we decide whether we want to support it.

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pathspec >= 0.12.1
2+
tomli >= 1.1.0; python_version < '3.11'

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ def find_version(*parts):
2020
with open("README.md") as f1, open("CHANGELOG.md") as f2:
2121
long_description = f1.read() + "\n\n" + f2.read()
2222

23+
with open("requirements.txt") as f:
24+
install_requires = f.read().splitlines()
25+
2326
setuptools.setup(
2427
name="vulture",
2528
version=find_version("vulture", "version.py"),
@@ -47,7 +50,7 @@ def find_version(*parts):
4750
"Programming Language :: Python :: Implementation :: PyPy",
4851
"Topic :: Software Development :: Quality Assurance",
4952
],
50-
install_requires=["tomli >= 1.1.0; python_version < '3.11'"],
53+
install_requires=install_requires,
5154
entry_points={"console_scripts": ["vulture = vulture.core:main"]},
5255
python_requires=">=3.8",
5356
packages=setuptools.find_packages(exclude=["tests"]),

tests/test_gitignore_patterns.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import os
2+
import pathlib
3+
import pytest
4+
import shutil
5+
6+
from . import call_vulture
7+
from vulture.utils import ExitCode
8+
9+
10+
class TempGitignore:
11+
def __init__(self, patterns):
12+
self.patterns = patterns
13+
root = pathlib.Path(".").resolve()
14+
self.file = root / ".gitignore"
15+
self.tmpfile = root / ".tmp_gitignore"
16+
17+
def __enter__(self):
18+
shutil.move(self.file, self.tmpfile)
19+
with open(self.file, "w") as f:
20+
f.write("\n".join(self.patterns))
21+
22+
return self.file
23+
24+
def __exit__(self, *args):
25+
os.remove(self.file)
26+
shutil.move(self.tmpfile, self.file)
27+
28+
29+
@pytest.fixture(scope="function")
30+
def gitignore(request):
31+
with TempGitignore(request.param) as fpath:
32+
yield fpath
33+
34+
35+
@pytest.mark.parametrize(
36+
"exclude_patterns,gitignore,exit_code",
37+
(
38+
([], [], ExitCode.NoDeadCode),
39+
([""], [], ExitCode.NoDeadCode),
40+
([], [""], ExitCode.NoDeadCode),
41+
([""], ["core.py", "utils.py"], ExitCode.NoDeadCode),
42+
(["core.py", "utils.py"], [""], ExitCode.DeadCode),
43+
([], ["core.py", "utils.py"], ExitCode.DeadCode),
44+
),
45+
indirect=("gitignore",),
46+
)
47+
def test_gitignore(exclude_patterns, gitignore, exit_code):
48+
def get_csv(paths):
49+
return ",".join(os.path.join("vulture", path) for path in paths)
50+
51+
cli_args = ["vulture/"]
52+
if exclude_patterns:
53+
cli_args.extend(["--exclude", get_csv(exclude_patterns)])
54+
55+
assert gitignore.is_file()
56+
assert call_vulture(cli_args) == exit_code

vulture/core.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import string
77
import sys
88

9+
from pathspec import PathSpec
910
from vulture import lines
1011
from vulture import noqa
1112
from vulture import utils
@@ -114,6 +115,13 @@ def _ignore_variable(filename, varname):
114115
)
115116

116117

118+
def _get_gitignore_pathspec():
119+
if (gitignore := Path(".gitignore").resolve()).is_file:
120+
with gitignore.open() as fh:
121+
return PathSpec.from_lines("gitwildmatch", fh)
122+
return PathSpec.from_lines("gitwildmatch", [])
123+
124+
117125
class Item:
118126
"""
119127
Hold the name, type and location of defined code.
@@ -263,9 +271,16 @@ def prepare_pattern(pattern):
263271
return pattern
264272

265273
exclude = [prepare_pattern(pattern) for pattern in (exclude or [])]
274+
gitignore = _get_gitignore_pathspec()
266275

267276
def exclude_path(path):
268-
return _match(path, exclude, case=False)
277+
# If no exclude patterns are provided via the CLI or
278+
# a TOML file, use .gitignore patterns to inform exclusion.
279+
return (
280+
_match(path, exclude, case=False)
281+
if exclude
282+
else gitignore.match_file(path)
283+
)
269284

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

0 commit comments

Comments
 (0)