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
18 changes: 12 additions & 6 deletions openviking/parse/gitignore.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,19 @@ def _transform_gitignore_line(line: str, base_rel: str) -> str:
return raw

scoped = pattern
if scoped.startswith("/"):
scoped = scoped.lstrip("/")
scoped = f"{base_rel}/{scoped}" if scoped else base_rel
elif "/" in scoped:
scoped = f"{base_rel}/{scoped}"
is_dir_only = scoped.endswith("/")
body = scoped.rstrip("/") if is_dir_only else scoped

if body.startswith("/"):
body = body.lstrip("/")
scoped = f"{base_rel}/{body}" if body else base_rel
elif "/" in body:
scoped = f"{base_rel}/{body}"
else:
scoped = f"{base_rel}/**/{scoped}" if scoped else base_rel
scoped = f"{base_rel}/**/{body}" if body else base_rel

if is_dir_only and scoped:
scoped = f"{scoped}/"

return f"!{scoped}" if negated else scoped

Expand Down
34 changes: 34 additions & 0 deletions tests/parse/test_gitignore_dir_anchoring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
# SPDX-License-Identifier: AGPL-3.0
"""Tests for _transform_gitignore_line directory-pattern anchoring.

A trailing-slash directory pattern (e.g. ``build/``) from a nested .gitignore
must remain unanchored (match anywhere under base_rel) instead of being scoped
directly to base_rel because the trailing ``/`` made it look like a path with
a separator.
"""

from openviking.parse.gitignore import _transform_gitignore_line


class TestTransformGitignoreDirAnchoring:
def test_dir_only_pattern_stays_unanchored(self):
"""``build/`` must expand to ``src/**/build/`` (match anywhere), not ``src/build/``."""
result = _transform_gitignore_line("build/", "src")
assert result == "src/**/build/"

def test_plain_pattern_without_slash_is_unanchored(self):
"""``build`` (no slash) matches anywhere under base_rel."""
assert _transform_gitignore_line("build", "src") == "src/**/build"

def test_leading_slash_pattern_is_anchored(self):
"""``/build`` is anchored directly to base_rel."""
assert _transform_gitignore_line("/build", "src") == "src/build"

def test_dir_only_with_leading_slash_stays_anchored(self):
"""``/build/`` stays anchored to base_rel but preserves the dir-only slash."""
assert _transform_gitignore_line("/build/", "src") == "src/build/"

def test_negated_dir_only_pattern_preserves_bang(self):
"""Negation prefix is preserved through the unanchored transform."""
assert _transform_gitignore_line("!build/", "src") == "!src/**/build/"