From 1d65c854858c066a70decebad6be463b3f8c4271 Mon Sep 17 00:00:00 2001 From: Jaen Date: Thu, 24 Jul 2025 23:23:17 +0300 Subject: [PATCH 1/3] Fix gitignore parsing for anchored patterns and escaped characters - Move escaped character handling after negation pattern detection - Fix is_anchored logic to only check for leading "/" instead of any "/" - Properly remove directory markers with removesuffix("/") Addresses gitignore parsing issues mentioned in #348 --- src/serena/util/file_system.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/serena/util/file_system.py b/src/serena/util/file_system.py index 327a2236..cf1baccd 100644 --- a/src/serena/util/file_system.py +++ b/src/serena/util/file_system.py @@ -185,10 +185,6 @@ class GitignoreParser: if not line or line.lstrip().startswith("#"): continue - # Handle escaped characters at the beginning - if line.startswith(("\\#", "\\!")): - line = line[1:] - # Store whether this is a negation pattern is_negation = line.startswith("!") if is_negation: @@ -200,13 +196,18 @@ class GitignoreParser: if not line: continue - # Determine if pattern is anchored to the gitignore directory - is_anchored = "/" in line[:-1] or line.startswith("/") - - # Remove leading slash for processing - if line.startswith("/"): + # Handle escaped characters at the beginning + if line.startswith(("\\#", "\\!")): line = line[1:] + # Determine if pattern is anchored to the gitignore directory and remove leading slash for processing + is_anchored = line.startswith("/") + if is_anchored: + line = line[1:] + + # Remove directory marker at the end since we do not support directory-only patterns + line = line.removesuffix("/") + # Adjust pattern based on gitignore file location if rel_dir: if is_anchored: From ba8f07d8366cddad29edd80ddab3e19ceff1edcf Mon Sep 17 00:00:00 2001 From: Jaen Date: Thu, 24 Jul 2025 23:35:54 +0300 Subject: [PATCH 2/3] Fix gitignore anchored pattern logic and update test - Use correct anchoring logic: only patterns starting with '/' are anchored - Fix test expectation for negation patterns to match Git behavior - Apply code formatting --- src/serena/util/file_system.py | 3 --- test/serena/util/test_file_system.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/serena/util/file_system.py b/src/serena/util/file_system.py index cf1baccd..4c667b0d 100644 --- a/src/serena/util/file_system.py +++ b/src/serena/util/file_system.py @@ -205,9 +205,6 @@ class GitignoreParser: if is_anchored: line = line[1:] - # Remove directory marker at the end since we do not support directory-only patterns - line = line.removesuffix("/") - # Adjust pattern based on gitignore file location if rel_dir: if is_anchored: diff --git a/test/serena/util/test_file_system.py b/test/serena/util/test_file_system.py index 3c9fd14e..97b9e3fd 100644 --- a/test/serena/util/test_file_system.py +++ b/test/serena/util/test_file_system.py @@ -369,7 +369,7 @@ debug.log assert "*.log" in patterns assert "!important.log" in patterns - assert "!/src/keep.log" in patterns + assert "!src/keep.log" in patterns def test_comments_and_empty_lines(self): """Test that comments and empty lines are ignored.""" From dcaf9ec992a528b15cf69ca0611608dca8aae11d Mon Sep 17 00:00:00 2001 From: Jaen Date: Thu, 24 Jul 2025 23:43:21 +0300 Subject: [PATCH 3/3] Add test for escaped negation pattern parsing Test demonstrates the fix for escaped character handling order: - Escaped negation patterns are treated as literals, not negations - Real negation patterns properly override ignore rules --- test/serena/util/test_file_system.py | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/serena/util/test_file_system.py b/test/serena/util/test_file_system.py index 97b9e3fd..2958a2c9 100644 --- a/test/serena/util/test_file_system.py +++ b/test/serena/util/test_file_system.py @@ -419,6 +419,38 @@ build/ assert "#not-a-comment.txt" in patterns assert "!not-negation.txt" in patterns + def test_escaped_negation_patterns(self): + test_dir = self.repo_path / "test_escaped_negation" + test_dir.mkdir() + + gitignore = test_dir / ".gitignore" + gitignore.write_text( + """*.log +\\!not-negation.log +!actual-negation.log +""" + ) + + parser = GitignoreParser(str(test_dir)) + specs = parser.get_ignore_specs() + + assert len(specs) == 1 + patterns = specs[0].patterns + + # Key assertions: escaped exclamation becomes literal, real negation preserved + assert "!not-negation.log" in patterns # escaped -> literal + assert "!actual-negation.log" in patterns # real negation preserved + + # Test the actual behavioral difference between escaped and real negation: + # *.log pattern should ignore test.log + assert parser.should_ignore("test.log") + + # Escaped negation file should still be ignored by *.log pattern + assert parser.should_ignore("!not-negation.log") + + # Actual negation should override the *.log pattern + assert not parser.should_ignore("actual-negation.log") + def test_glob_patterns(self): """Test various glob patterns work correctly.""" test_dir = self.repo_path / "test_glob"