Unify glob matching to always use regex translation

This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-07-23 15:28:48 +02:00
1 parent 33125287dc
commit 7033e90306
2 files changed
+36 -35

No files matched your search

+21 -33
View File
@@ -1,4 +1,3 @@
import fnmatch
import hashlib
import logging
import re
@@ -214,7 +213,7 @@ def expand_braces(pattern: str) -> list[str]:
@lru_cache(maxsize=256)
def _translate_glob_no_double_star(pattern: str) -> str:
def _translate_glob_to_regex(pattern: str) -> str:
res = []
i = 0
n = len(pattern)
@@ -222,8 +221,22 @@ def _translate_glob_no_double_star(pattern: str) -> str:
c = pattern[i]
i += 1
if c == "*":
if not res or res[-1] != "[^/]*":
res.append("[^/]*")
if i < n and pattern[i] == "*": # **
i += 1
if i < n and pattern[i] == "/": # **/
i += 1
if res and res[-1] == "/": # /**/
res = res[:-1]
res.append("(?:/|/.*/)")
elif res: # c**/ (with c != '/')
res.append(".*/")
else: # **/ at the start of the pattern
res.append("(?:.*/)?")
else: # c**d (with c, d != '/')
res.append(".*")
else: # single *
if not res or res[-1] != "[^/]*":
res.append("[^/]*")
elif c == "?":
res.append("[^/]")
elif c == "[":
@@ -271,36 +284,11 @@ def glob_match(pattern: str, path: str) -> bool:
:param path: File path to match against
:return: True if path matches pattern
"""
pattern = pattern.replace("\\", "/") # Normalize backslashes to forward slashes
path = path.replace("\\", "/") # Normalize path backslashes to forward slashes
# normalise backslashes to forward slashes in both path and pattern
pattern = pattern.replace("\\", "/")
path = path.replace("\\", "/")
# Handle ** patterns that should match zero or more directories
if "**" in pattern:
# Method 1: Standard fnmatch (matches one or more directories)
regex1 = fnmatch.translate(pattern)
if re.match(regex1, path):
return True
# Method 2: Handle zero-directory case by removing /** entirely
# Convert "src/**/test.py" to "src/test.py"
if "/**/" in pattern:
zero_dir_pattern = pattern.replace("/**/", "/")
regex2 = fnmatch.translate(zero_dir_pattern)
if re.match(regex2, path):
return True
# Method 3: Handle leading ** case by removing **/
# Convert "**/test.py" to "test.py"
if pattern.startswith("**/"):
zero_dir_pattern = pattern[3:] # Remove "**/"
regex3 = fnmatch.translate(zero_dir_pattern)
if re.match(regex3, path):
return True
return False
else:
# Simple pattern without **, translate bare * and ? to not match /
return bool(re.match(_translate_glob_no_double_star(pattern), path))
return bool(re.match(_translate_glob_to_regex(pattern), path))
class GlobMatcher:
+15 -2
View File
@@ -517,17 +517,21 @@ class TestGlobMatch:
("src/*.py", "src/sub/file.py", False),
# Double asterisk patterns
("**agent.py", "agent.py", True),
("**/agent.py", "agent.py", True),
("**/agent.py", "src/serena/agent.py", True),
("src/**/agent.py", "src/agent.py", True),
("src/**/agent.py", "src/serena/foo/agent.py", True),
("src/s**a/agent.py", "src/serena/agent.py", True),
("src/s**a/agent.py", "src/serena/a/agent.py", True),
("**agent.py", "src/agent.py", True),
("**agent.py", "src/serena/agent.py", True),
("**agent.py", "src/serena/process_isolated_agent.py", True),
("**agent.py", "agent_test.py", False),
# Prefix with double asterisk
("src/**agent.py", "src/agent.py", True),
("src/**agent.py", "src/serena/agent.py", True),
("src/**agent.py", "src/serena/process_isolated_agent.py", True),
("src/**agent.py", "other/agent.py", False),
("src/**agent.py", "src/agent_test.py", False),
# Directory patterns
("src/**", "src/file.py", True),
("src/**", "src/dir/file.py", True),
("src/**", "other/file.py", False),
@@ -538,6 +542,15 @@ class TestGlobMatch:
# Simple patterns without asterisks
("src/file.py", "src/file.py", True),
("src/file.py", "src/other.py", False),
# Patterns with backslash
("src\\file.py", "src/file.py", True),
("src\\file.py", "src\\file.py", True),
("src\\file.py", "src/other.py", False),
# Patterns with []
("file[0-9].py", "file1.py", True),
("file[0-9].py", "filea.py", False),
("file[!0-9].py", "filea.py", True),
("file[!0-9].py", "file1.py", False),
],
)
def test_glob_match(self, pattern, path, expected):