Skip to content

Commit 4fbf23b

Browse files
committed
migrate to ignore_utils; add tests; remove unused pathspec; remove .cycodeignore
1 parent 85993ff commit 4fbf23b

File tree

7 files changed

+212
-105
lines changed

7 files changed

+212
-105
lines changed

cycode/cli/files_collector/path_documents.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import os
2-
from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple
3-
4-
import pathspec
2+
from typing import TYPE_CHECKING, List, Tuple
53

64
from cycode.cli.files_collector.excluder import exclude_irrelevant_files
75
from cycode.cli.files_collector.iac.tf_content_generator import (
@@ -30,7 +28,7 @@ def _get_all_existing_files_in_directory(path: str, *, walk_with_ignore_patterns
3028
return files
3129

3230

33-
def _get_relevant_files_in_path(path: str, exclude_patterns: Optional[Iterable[str]] = None) -> List[str]:
31+
def _get_relevant_files_in_path(path: str) -> List[str]:
3432
absolute_path = get_absolute_path(path)
3533

3634
if not os.path.isfile(absolute_path) and not os.path.isdir(absolute_path):
@@ -40,11 +38,6 @@ def _get_relevant_files_in_path(path: str, exclude_patterns: Optional[Iterable[s
4038
return [absolute_path]
4139

4240
file_paths = _get_all_existing_files_in_directory(absolute_path)
43-
44-
if exclude_patterns:
45-
path_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, exclude_patterns)
46-
file_paths = path_spec.match_files(file_paths, negate=True)
47-
4841
return [file_path for file_path in file_paths if os.path.isfile(file_path)]
4942

5043

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import os
2-
from collections import defaultdict
32
from typing import Generator, Iterable, List, Tuple
43

5-
import pathspec
6-
from pathspec.util import StrPath
7-
8-
from cycode.cli.utils.path_utils import get_file_content
4+
from cycode.cli.utils.ignore_utils import IgnoreFilterManager
95
from cycode.cyclient import logger
106

11-
_SUPPORTED_IGNORE_PATTERN_FILES = {'.gitignore', '.cycodeignore'}
7+
_SUPPORTED_IGNORE_PATTERN_FILES = { # oneday we will bring .cycodeignore or something like that
8+
'.gitignore',
9+
}
1210
_DEFAULT_GLOBAL_IGNORE_PATTERNS = [
13-
'**/.git',
14-
'**/.cycode',
11+
'.git',
12+
'.cycode',
1513
]
1614

1715

@@ -35,44 +33,10 @@ def _collect_top_level_ignore_files(path: str) -> List[str]:
3533
return ignore_files
3634

3735

38-
def _get_global_ignore_patterns(path: str) -> List[str]:
39-
ignore_patterns = _DEFAULT_GLOBAL_IGNORE_PATTERNS.copy()
40-
for ignore_file in _collect_top_level_ignore_files(path):
41-
file_patterns = get_file_content(ignore_file).splitlines()
42-
ignore_patterns.extend(file_patterns)
43-
return ignore_patterns
44-
45-
46-
def _should_include_path(ignore_patterns: List[str], path: StrPath) -> bool:
47-
path_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, ignore_patterns)
48-
return not path_spec.match_file(path) # works with both files and directories; negative match
49-
50-
5136
def walk_ignore(path: str) -> Generator[Tuple[str, List[str], List[str]], None, None]:
52-
global_ignore_patterns = _get_global_ignore_patterns(path)
53-
path_to_ignore_patterns = defaultdict(list)
54-
55-
for dirpath, dirnames, filenames in os.walk(path, topdown=True):
56-
# finds and processes ignore files first to get the patterns
57-
for filename in filenames:
58-
filepath = os.path.join(dirpath, filename)
59-
if filename in _SUPPORTED_IGNORE_PATTERN_FILES:
60-
logger.debug('Apply ignore file: %s', filepath)
61-
62-
parent_dir = os.path.dirname(dirpath)
63-
if dirpath not in path_to_ignore_patterns and parent_dir in path_to_ignore_patterns:
64-
# inherit ignore patterns from parent directory on first occurrence
65-
logger.debug('Inherit ignore patterns: %s', {'inherit_from': parent_dir, 'inherit_to': dirpath})
66-
path_to_ignore_patterns[dirpath].extend(path_to_ignore_patterns[parent_dir])
67-
68-
# always read ignore patterns for the current directory
69-
path_to_ignore_patterns[dirpath].extend(get_file_content(filepath).splitlines())
70-
71-
ignore_patterns = global_ignore_patterns + path_to_ignore_patterns.get(dirpath, [])
72-
73-
# decrease recursion depth of os.walk() because of topdown=True by changing the list in-place
74-
# slicing ([:]) is mandatory to change dict in-place!
75-
dirnames[:] = [d for d in dirnames if _should_include_path(ignore_patterns, os.path.join(dirpath, d))]
76-
filenames[:] = [f for f in filenames if _should_include_path(ignore_patterns, os.path.join(dirpath, f))]
77-
78-
yield dirpath, dirnames, filenames
37+
ignore_filter_manager = IgnoreFilterManager.build(
38+
path=path,
39+
global_ignore_file_paths=_collect_top_level_ignore_files(path),
40+
global_patterns=_DEFAULT_GLOBAL_IGNORE_PATTERNS,
41+
)
42+
yield from ignore_filter_manager.walk()

cycode/cli/utils/ignore_utils.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,11 @@ def read_ignore_patterns(f: BinaryIO) -> Iterable[bytes]:
132132
f: File-like object to read from
133133
Returns: List of patterns
134134
"""
135-
136135
for line in f:
137136
line = line.rstrip(b'\r\n')
138137

139138
# Ignore blank lines, they're used for readability.
140-
if not line:
139+
if not line.strip():
141140
continue
142141

143142
if line.startswith(b'#'):
@@ -397,7 +396,9 @@ def walk(self, **kwargs) -> Generator[Tuple[str, List[str], List[str]], None, No
397396

398397
# decrease recursion depth of os.walk() by ignoring subdirectories because of topdown=True
399398
# slicing ([:]) is mandatory to change dict in-place!
400-
dirnames[:] = [dirname for dirname in dirnames if not self.is_ignored(os.path.join(rel_dirpath, dirname, ''))]
399+
dirnames[:] = [
400+
dirname for dirname in dirnames if not self.is_ignored(os.path.join(rel_dirpath, dirname, ''))
401+
]
401402

402403
# remove ignored files
403404
filenames = [os.path.basename(f) for f in filenames if not self.is_ignored(os.path.join(rel_dirpath, f))]
@@ -430,6 +431,13 @@ def build(
430431
if not global_patterns:
431432
global_patterns = []
432433

434+
global_ignore_file_paths.extend(
435+
[
436+
os.path.join('.git', 'info', 'exclude'), # relative to an input path, so within the repo
437+
os.path.expanduser(os.path.join('~', '.config', 'git', 'ignore')), # absolute
438+
]
439+
)
440+
433441
if hasattr(path, '__fspath__'):
434442
path = path.__fspath__()
435443

poetry.lock

Lines changed: 1 addition & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ click = ">=8.1.0,<8.2.0"
3232
colorama = ">=0.4.3,<0.5.0"
3333
pyyaml = ">=6.0,<7.0"
3434
marshmallow = ">=3.15.0,<3.23.0" # 3.23 dropped support for Python 3.8
35-
pathspec = ">=0.11.1,<0.13.0"
3635
gitpython = ">=3.1.30,<3.2.0"
3736
arrow = ">=1.0.0,<1.4.0"
3837
binaryornot = ">=0.4.4,<0.5.0"

tests/cli/files_collector/test_walk_ignore.py

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from cycode.cli.files_collector.walk_ignore import (
66
_collect_top_level_ignore_files,
7-
_get_global_ignore_patterns,
87
_walk_to_top,
98
walk_ignore,
109
)
@@ -52,11 +51,9 @@ def _create_mocked_file_structure(fs: 'FakeFilesystem') -> None:
5251
fs.create_dir('/home/user/project/.git')
5352
fs.create_file('/home/user/project/.git/HEAD')
5453

55-
fs.create_file('/home/user/project/.gitignore', contents='*.pyc')
54+
fs.create_file('/home/user/project/.gitignore', contents='*.pyc\n*.log')
5655
fs.create_file('/home/user/project/ignored.pyc')
5756
fs.create_file('/home/user/project/presented.txt')
58-
59-
fs.create_file('/home/user/project/.cycodeignore', contents='*.log')
6057
fs.create_file('/home/user/project/ignored2.log')
6158
fs.create_file('/home/user/project/ignored2.pyc')
6259
fs.create_file('/home/user/project/presented2.txt')
@@ -75,45 +72,27 @@ def test_collect_top_level_ignore_files(fs: 'FakeFilesystem') -> None:
7572
# Test with path inside the project
7673
path = normpath('/home/user/project/subproject')
7774
ignore_files = _collect_top_level_ignore_files(path)
78-
79-
assert len(ignore_files) == 3
75+
assert len(ignore_files) == 2
8076
assert normpath('/home/user/project/subproject/.gitignore') in ignore_files
8177
assert normpath('/home/user/project/.gitignore') in ignore_files
82-
assert normpath('/home/user/project/.cycodeignore') in ignore_files
83-
84-
# Test with a path that does not have any ignore files
85-
fs.remove('/home/user/project/.gitignore')
86-
path = normpath('/home/user')
87-
ignore_files = _collect_top_level_ignore_files(path)
88-
89-
assert len(ignore_files) == 0
9078

9179
# Test with path at the top level with no ignore files
9280
path = normpath('/home/user/.git')
9381
ignore_files = _collect_top_level_ignore_files(path)
94-
9582
assert len(ignore_files) == 0
9683

9784
# Test with path at the top level with a .gitignore
9885
path = normpath('/home/user/project')
9986
ignore_files = _collect_top_level_ignore_files(path)
100-
10187
assert len(ignore_files) == 1
102-
assert normpath('/home/user/project/.cycodeignore') in ignore_files
103-
104-
105-
def test_get_global_ignore_patterns(fs: 'FakeFilesystem') -> None:
106-
_create_mocked_file_structure(fs)
107-
ignore_patterns = _get_global_ignore_patterns('/home/user/project/subproject')
88+
assert normpath('/home/user/project/.gitignore') in ignore_files
10889

109-
assert len(ignore_patterns) == 5
110-
# default global:
111-
assert '**/.git' in ignore_patterns
112-
assert '**/.cycode' in ignore_patterns
113-
# additional:
114-
assert '*.txt' in ignore_patterns
115-
assert '*.pyc' in ignore_patterns
116-
assert '*.log' in ignore_patterns
90+
# Test with a path that does not have any ignore files
91+
fs.remove('/home/user/project/.gitignore')
92+
path = normpath('/home/user')
93+
ignore_files = _collect_top_level_ignore_files(path)
94+
assert len(ignore_files) == 0
95+
fs.create_file('/home/user/project/.gitignore', contents='*.pyc\n*.log')
11796

11897

11998
def _collect_walk_ignore_files(path: str) -> List[str]:
@@ -131,7 +110,7 @@ def test_walk_ignore(fs: 'FakeFilesystem') -> None:
131110
path = normpath('/home/user/project')
132111
result = _collect_walk_ignore_files(path)
133112

134-
assert len(result) == 6
113+
assert len(result) == 5
135114
# ignored globally by default:
136115
assert normpath('/home/user/project/.git/HEAD') not in result
137116
assert normpath('/home/user/project/.cycode/config.yaml') not in result
@@ -146,7 +125,6 @@ def test_walk_ignore(fs: 'FakeFilesystem') -> None:
146125
assert normpath('/home/user/project/subproject/ignored.log') not in result
147126
# presented after both .gitignore and .cycodeignore:
148127
assert normpath('/home/user/project/.gitignore') in result
149-
assert normpath('/home/user/project/.cycodeignore') in result
150128
assert normpath('/home/user/project/subproject/.gitignore') in result
151129
assert normpath('/home/user/project/presented.txt') in result
152130
assert normpath('/home/user/project/presented2.txt') in result

0 commit comments

Comments
 (0)