tree-sitter-analyzer 1.9.17.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Gitignore Detection Utility
|
|
4
|
+
|
|
5
|
+
Intelligently detects when .gitignore rules might interfere with file searches
|
|
6
|
+
and suggests using --no-ignore option when appropriate.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GitignoreDetector:
|
|
17
|
+
"""Detects .gitignore interference with file searches"""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self.common_ignore_patterns = {
|
|
21
|
+
# Directory patterns that commonly cause search issues
|
|
22
|
+
"build/*",
|
|
23
|
+
"dist/*",
|
|
24
|
+
"node_modules/*",
|
|
25
|
+
"__pycache__/*",
|
|
26
|
+
"target/*",
|
|
27
|
+
".git/*",
|
|
28
|
+
".svn/*",
|
|
29
|
+
".hg/*",
|
|
30
|
+
"code/*",
|
|
31
|
+
"src/*",
|
|
32
|
+
"lib/*",
|
|
33
|
+
"app/*", # Added code/* which is our case
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def should_use_no_ignore(
|
|
37
|
+
self, roots: list[str], project_root: str | None = None
|
|
38
|
+
) -> bool:
|
|
39
|
+
"""
|
|
40
|
+
Determine if --no-ignore should be used based on search context
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
roots: List of root directories to search
|
|
44
|
+
project_root: Optional project root directory
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
True if --no-ignore should be used
|
|
48
|
+
"""
|
|
49
|
+
# Only apply auto-detection for root directory searches
|
|
50
|
+
if not (len(roots) == 1 and roots[0] in [".", "./"]):
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
if not project_root:
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
project_path = Path(project_root).resolve()
|
|
58
|
+
|
|
59
|
+
# Check for .gitignore files that might interfere
|
|
60
|
+
gitignore_files = self._find_gitignore_files(project_path)
|
|
61
|
+
|
|
62
|
+
for gitignore_file in gitignore_files:
|
|
63
|
+
# Use the directory containing the .gitignore as the reference point
|
|
64
|
+
gitignore_dir = gitignore_file.parent
|
|
65
|
+
if self._has_interfering_patterns(
|
|
66
|
+
gitignore_file, gitignore_dir, project_path
|
|
67
|
+
):
|
|
68
|
+
logger.debug(
|
|
69
|
+
f"Found interfering .gitignore patterns in {gitignore_file}"
|
|
70
|
+
)
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.warning(f"Error detecting .gitignore interference: {e}")
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def _find_gitignore_files(self, project_path: Path) -> list[Path]:
|
|
80
|
+
"""Find .gitignore files in project hierarchy"""
|
|
81
|
+
gitignore_files = []
|
|
82
|
+
|
|
83
|
+
# Check current directory and parent directories
|
|
84
|
+
current = project_path
|
|
85
|
+
max_depth = 3 # Limit search depth
|
|
86
|
+
|
|
87
|
+
# For temporary directories (like test directories), only check the current directory
|
|
88
|
+
# to avoid finding .gitignore files in parent directories that are not part of the test
|
|
89
|
+
if "tmp" in str(current).lower() or "temp" in str(current).lower():
|
|
90
|
+
gitignore_path = current / ".gitignore"
|
|
91
|
+
if gitignore_path.exists():
|
|
92
|
+
gitignore_files.append(gitignore_path)
|
|
93
|
+
return gitignore_files
|
|
94
|
+
|
|
95
|
+
for _ in range(max_depth):
|
|
96
|
+
gitignore_path = current / ".gitignore"
|
|
97
|
+
if gitignore_path.exists():
|
|
98
|
+
gitignore_files.append(gitignore_path)
|
|
99
|
+
|
|
100
|
+
parent = current.parent
|
|
101
|
+
if parent == current: # Reached root
|
|
102
|
+
break
|
|
103
|
+
current = parent
|
|
104
|
+
|
|
105
|
+
return gitignore_files
|
|
106
|
+
|
|
107
|
+
def _has_interfering_patterns(
|
|
108
|
+
self, gitignore_file: Path, gitignore_dir: Path, current_search_dir: Path
|
|
109
|
+
) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Check if .gitignore file has patterns that might interfere with searches
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
gitignore_file: Path to the .gitignore file
|
|
115
|
+
gitignore_dir: Directory containing the .gitignore file
|
|
116
|
+
current_search_dir: Directory where the search is being performed
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
from ...encoding_utils import read_file_safe
|
|
120
|
+
|
|
121
|
+
content, _ = read_file_safe(gitignore_file)
|
|
122
|
+
lines = content.splitlines()
|
|
123
|
+
|
|
124
|
+
for line in lines:
|
|
125
|
+
line = line.strip()
|
|
126
|
+
|
|
127
|
+
# Skip comments and empty lines
|
|
128
|
+
if not line or line.startswith("#"):
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
# Check for patterns that commonly cause search issues
|
|
132
|
+
if self._is_interfering_pattern(
|
|
133
|
+
line, gitignore_dir, current_search_dir
|
|
134
|
+
):
|
|
135
|
+
logger.debug(f"Found interfering pattern: {line}")
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.warning(f"Error reading .gitignore file {gitignore_file}: {e}")
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
def _is_interfering_pattern(
|
|
145
|
+
self, pattern: str, gitignore_dir: Path, current_search_dir: Path
|
|
146
|
+
) -> bool:
|
|
147
|
+
"""
|
|
148
|
+
Check if a gitignore pattern is likely to interfere with searches
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
pattern: The gitignore pattern
|
|
152
|
+
gitignore_dir: Directory containing the .gitignore file
|
|
153
|
+
current_search_dir: Directory where the search is being performed
|
|
154
|
+
"""
|
|
155
|
+
# Remove leading slash
|
|
156
|
+
pattern = pattern.lstrip("/")
|
|
157
|
+
|
|
158
|
+
# Check for broad directory exclusions that contain searchable files
|
|
159
|
+
if pattern.endswith("/*") or pattern.endswith("/"):
|
|
160
|
+
dir_name = pattern.rstrip("/*")
|
|
161
|
+
|
|
162
|
+
# Check if the pattern affects the current search directory
|
|
163
|
+
pattern_dir = gitignore_dir / dir_name
|
|
164
|
+
|
|
165
|
+
# If we're searching in a subdirectory that would be ignored by this pattern
|
|
166
|
+
if self._is_search_dir_affected_by_pattern(
|
|
167
|
+
current_search_dir, pattern_dir, gitignore_dir
|
|
168
|
+
):
|
|
169
|
+
# For testing purposes, consider it interfering if the directory exists
|
|
170
|
+
if pattern_dir.exists() and pattern_dir.is_dir():
|
|
171
|
+
logger.debug(
|
|
172
|
+
f"Pattern '{pattern}' interferes with search - directory exists"
|
|
173
|
+
)
|
|
174
|
+
return True
|
|
175
|
+
|
|
176
|
+
# Check for patterns that ignore entire source directories
|
|
177
|
+
source_dirs = [
|
|
178
|
+
"code",
|
|
179
|
+
"src",
|
|
180
|
+
"lib",
|
|
181
|
+
"app",
|
|
182
|
+
"main",
|
|
183
|
+
"java",
|
|
184
|
+
"python",
|
|
185
|
+
"js",
|
|
186
|
+
"ts",
|
|
187
|
+
]
|
|
188
|
+
pattern_dir_name = pattern.rstrip("/*")
|
|
189
|
+
if pattern_dir_name in source_dirs:
|
|
190
|
+
# Always consider source directory patterns as interfering
|
|
191
|
+
logger.debug(
|
|
192
|
+
f"Pattern '{pattern}' interferes with search - ignores source directory"
|
|
193
|
+
)
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
# Check for leading slash patterns (absolute paths from repo root)
|
|
197
|
+
if pattern.startswith("/") or "/" in pattern:
|
|
198
|
+
logger.debug(
|
|
199
|
+
f"Pattern '{pattern}' interferes with search - absolute path pattern"
|
|
200
|
+
)
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
def _is_search_dir_affected_by_pattern(
|
|
206
|
+
self, search_dir: Path, pattern_dir: Path, gitignore_dir: Path
|
|
207
|
+
) -> bool:
|
|
208
|
+
"""Check if the search directory would be affected by a gitignore pattern"""
|
|
209
|
+
try:
|
|
210
|
+
# Check if paths exist before resolving
|
|
211
|
+
if not search_dir.exists() or not pattern_dir.exists():
|
|
212
|
+
logger.debug(
|
|
213
|
+
f"Path does not exist: {search_dir} or {pattern_dir}, assuming affected"
|
|
214
|
+
)
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
# If search_dir is the same as pattern_dir or is a subdirectory of pattern_dir
|
|
218
|
+
search_resolved = search_dir.resolve()
|
|
219
|
+
pattern_resolved = pattern_dir.resolve()
|
|
220
|
+
|
|
221
|
+
# Check if we're searching in the directory that would be ignored
|
|
222
|
+
return search_resolved == pattern_resolved or str(
|
|
223
|
+
search_resolved
|
|
224
|
+
).startswith(str(pattern_resolved) + os.sep)
|
|
225
|
+
except (OSError, ValueError, RuntimeError):
|
|
226
|
+
# If path resolution fails, assume it could be affected
|
|
227
|
+
logger.debug(
|
|
228
|
+
f"Path resolution failed for {search_dir} or {pattern_dir}, assuming affected"
|
|
229
|
+
)
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
def _directory_has_searchable_files(self, directory: Path) -> bool:
|
|
233
|
+
"""Check if directory contains files that users typically want to search"""
|
|
234
|
+
searchable_extensions = {
|
|
235
|
+
".java",
|
|
236
|
+
".py",
|
|
237
|
+
".js",
|
|
238
|
+
".ts",
|
|
239
|
+
".cpp",
|
|
240
|
+
".c",
|
|
241
|
+
".h",
|
|
242
|
+
".cs",
|
|
243
|
+
".go",
|
|
244
|
+
".rs",
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
# Quick check - look for any files with searchable extensions
|
|
249
|
+
for file_path in directory.rglob("*"):
|
|
250
|
+
if (
|
|
251
|
+
file_path.is_file()
|
|
252
|
+
and file_path.suffix.lower() in searchable_extensions
|
|
253
|
+
):
|
|
254
|
+
return True
|
|
255
|
+
return False
|
|
256
|
+
except Exception:
|
|
257
|
+
# If we can't scan, assume it might have searchable files
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
def get_detection_info(
|
|
261
|
+
self, roots: list[str], project_root: str | None = None
|
|
262
|
+
) -> dict[str, object]:
|
|
263
|
+
"""
|
|
264
|
+
Get detailed information about gitignore detection
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Dictionary with detection details for debugging/logging
|
|
268
|
+
"""
|
|
269
|
+
info: dict[str, object] = {
|
|
270
|
+
"should_use_no_ignore": False,
|
|
271
|
+
"detected_gitignore_files": [],
|
|
272
|
+
"interfering_patterns": [],
|
|
273
|
+
"reason": "No interference detected",
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if not (len(roots) == 1 and roots[0] in [".", "./"]):
|
|
277
|
+
info["reason"] = "Not a root directory search"
|
|
278
|
+
return info
|
|
279
|
+
|
|
280
|
+
if not project_root:
|
|
281
|
+
info["reason"] = "No project root specified"
|
|
282
|
+
return info
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
project_path = Path(project_root).resolve()
|
|
286
|
+
# Check if project path exists
|
|
287
|
+
if not project_path.exists():
|
|
288
|
+
raise FileNotFoundError(f"Project root does not exist: {project_root}")
|
|
289
|
+
|
|
290
|
+
gitignore_files = self._find_gitignore_files(project_path)
|
|
291
|
+
info["detected_gitignore_files"] = [str(f) for f in gitignore_files]
|
|
292
|
+
|
|
293
|
+
for gitignore_file in gitignore_files:
|
|
294
|
+
gitignore_dir = gitignore_file.parent
|
|
295
|
+
patterns = self._get_interfering_patterns(
|
|
296
|
+
gitignore_file, gitignore_dir, project_path
|
|
297
|
+
)
|
|
298
|
+
if patterns:
|
|
299
|
+
existing_patterns = info.get("interfering_patterns", [])
|
|
300
|
+
if isinstance(existing_patterns, list):
|
|
301
|
+
info["interfering_patterns"] = existing_patterns + patterns
|
|
302
|
+
else:
|
|
303
|
+
info["interfering_patterns"] = patterns
|
|
304
|
+
|
|
305
|
+
interfering_patterns = info.get("interfering_patterns", [])
|
|
306
|
+
if interfering_patterns:
|
|
307
|
+
info["should_use_no_ignore"] = True
|
|
308
|
+
pattern_count = (
|
|
309
|
+
len(interfering_patterns)
|
|
310
|
+
if isinstance(interfering_patterns, list)
|
|
311
|
+
else 0
|
|
312
|
+
)
|
|
313
|
+
info["reason"] = f"Found {pattern_count} interfering patterns"
|
|
314
|
+
|
|
315
|
+
except Exception as e:
|
|
316
|
+
info["reason"] = f"Error during detection: {e}"
|
|
317
|
+
|
|
318
|
+
return info
|
|
319
|
+
|
|
320
|
+
def _get_interfering_patterns(
|
|
321
|
+
self, gitignore_file: Path, gitignore_dir: Path, current_search_dir: Path
|
|
322
|
+
) -> list[str]:
|
|
323
|
+
"""Get list of interfering patterns from a gitignore file"""
|
|
324
|
+
interfering = []
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
from ...encoding_utils import read_file_safe
|
|
328
|
+
|
|
329
|
+
content, _ = read_file_safe(gitignore_file)
|
|
330
|
+
lines = content.splitlines()
|
|
331
|
+
|
|
332
|
+
for line in lines:
|
|
333
|
+
line = line.strip()
|
|
334
|
+
if (
|
|
335
|
+
line
|
|
336
|
+
and not line.startswith("#")
|
|
337
|
+
and self._is_interfering_pattern(
|
|
338
|
+
line, gitignore_dir, current_search_dir
|
|
339
|
+
)
|
|
340
|
+
):
|
|
341
|
+
interfering.append(line)
|
|
342
|
+
|
|
343
|
+
except Exception as e:
|
|
344
|
+
logger.warning(f"Error reading .gitignore file {gitignore_file}: {e}")
|
|
345
|
+
|
|
346
|
+
return interfering
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# Global instance for easy access
|
|
350
|
+
_default_detector = None
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def get_default_detector() -> GitignoreDetector:
|
|
354
|
+
"""Get the default gitignore detector instance"""
|
|
355
|
+
global _default_detector
|
|
356
|
+
if _default_detector is None:
|
|
357
|
+
_default_detector = GitignoreDetector()
|
|
358
|
+
return _default_detector
|