Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions code_review_graph/incremental.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,56 @@
# Default ignore patterns (in addition to .gitignore)
DEFAULT_IGNORE_PATTERNS = [
".code-review-graph/**",
"node_modules/**",
".git/**",
# JavaScript / TypeScript / Node
"node_modules/**",
".next/**",
".nuxt/**",
# Python
"__pycache__/**",
"*.pyc",
".venv/**",
"venv/**",
# PHP / Laravel / Composer
"vendor/**",
"storage/**",
"bootstrap/cache/**",
"public/build/**",
# Ruby / Rails
"vendor/bundle/**",
".bundle/**",
# Java / Kotlin / Gradle / Maven
".gradle/**",
"*.jar",
# .NET / C#
"bin/**",
"obj/**",
"packages/**",
# Rust
"target/**",
# Dart / Flutter
".dart_tool/**",
".pub-cache/**",
# Build outputs
"dist/**",
"build/**",
".next/**",
"target/**",
"coverage/**",
# Minified / generated
"*.min.js",
"*.min.css",
"*.map",
"*.lock",
"package-lock.json",
"yarn.lock",
# Database files
"*.db",
"*.sqlite",
"*.db-journal",
"*.db-wal",
# Misc
".cache/**",
".tmp/**",
"tmp/**",
]


Expand Down Expand Up @@ -116,8 +146,26 @@ def _load_ignore_patterns(repo_root: Path) -> list[str]:


def _should_ignore(path: str, patterns: list[str]) -> bool:
"""Check if a path matches any ignore pattern."""
return any(fnmatch.fnmatch(path, p) for p in patterns)
"""Check if a path matches any ignore pattern.

For ** patterns like 'node_modules/**', matches any path segment — not just
the root. This ensures nested dependency directories (e.g.,
'packages/app/node_modules/react/index.js') are correctly ignored in
monorepos and workspaces.
"""
from pathlib import PurePosixPath

pp = PurePosixPath(path)
for p in patterns:
if "**" in p:
prefix = p.split("/**")[0]
if any(part == prefix for part in pp.parts) or fnmatch.fnmatch(path, p):
return True
elif fnmatch.fnmatch(path, p) or any(
fnmatch.fnmatch(part, p) for part in pp.parts
):
return True
return False


def _is_binary(path: Path) -> bool:
Expand Down
33 changes: 33 additions & 0 deletions tests/test_incremental.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,39 @@ def test_should_ignore_matches(self):
assert _should_ignore(".git/HEAD", patterns)
assert not _should_ignore("src/main.py", patterns)

def test_should_ignore_nested_paths(self):
"""Nested dependency dirs (monorepos/workspaces) must be ignored."""
patterns = ["node_modules/**", "vendor/**", "storage/**"]
# Nested node_modules (monorepo workspace)
assert _should_ignore("packages/app/node_modules/react/index.js", patterns)
# Nested vendor (PHP monorepo)
assert _should_ignore("services/api/vendor/guzzlehttp/Client.php", patterns)
# Nested storage
assert _should_ignore("app/storage/logs/laravel.log", patterns)
# Source files must not be affected
assert not _should_ignore("packages/app/src/main.ts", patterns)
assert not _should_ignore("src/vendors/custom.php", patterns)

def test_should_ignore_framework_patterns(self):
"""Framework-specific dirs from DEFAULT_IGNORE_PATTERNS."""
from code_review_graph.incremental import DEFAULT_IGNORE_PATTERNS

patterns = DEFAULT_IGNORE_PATTERNS
# PHP / Laravel
assert _should_ignore("vendor/laravel/framework/src/Collection.php", patterns)
assert _should_ignore("storage/logs/laravel.log", patterns)
assert _should_ignore("bootstrap/cache/packages.php", patterns)
# .NET
assert _should_ignore("bin/Debug/net8.0/app.dll", patterns)
assert _should_ignore("obj/Release/app.assets.cache", patterns)
# Dart / Flutter
assert _should_ignore(".dart_tool/package_config.json", patterns)
# Java / Gradle
assert _should_ignore(".gradle/caches/transforms-3/file.jar", patterns)
# Source files untouched
assert not _should_ignore("src/app/page.tsx", patterns)
assert not _should_ignore("app/Http/Controllers/UserController.php", patterns)


class TestIsBinary:
def test_text_file_is_not_binary(self, tmp_path):
Expand Down