diff --git a/src/serena/util/file_system.py b/src/serena/util/file_system.py index 327a2236..4c667b0d 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,11 +196,13 @@ class GitignoreParser: if not line: continue - # Determine if pattern is anchored to the gitignore directory - is_anchored = "/" in line[:-1] or line.startswith("/") + # Handle escaped characters at the beginning + if line.startswith(("\\#", "\\!")): + line = line[1:] - # Remove leading slash for processing - if line.startswith("/"): + # 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:] # Adjust pattern based on gitignore file location diff --git a/test/serena/util/test_file_system.py b/test/serena/util/test_file_system.py index 3c9fd14e..2958a2c9 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.""" @@ -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"