Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ on:

jobs:
test:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ['3.11', '3.10', '3.9', '3.8', '3.7']
python-version: ['3.12', '3.11', '3.10', '3.9', '3.8', '3.7']
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down
21 changes: 19 additions & 2 deletions gitignore_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from os.path import abspath, dirname
from pathlib import Path
import sys
from typing import Reversible, Union

def handle_negation(file_path, rules: Reversible["IgnoreRule"]):
Expand Down Expand Up @@ -125,9 +126,14 @@ def __repr__(self):
def match(self, abs_path: Union[str, Path]):
matched = False
if self.base_path:
rel_path = str(_normalize_path(abs_path).relative_to(self.base_path))
rel_path = _normalize_path(abs_path).relative_to(self.base_path).as_posix()
else:
rel_path = str(_normalize_path(abs_path))
rel_path = _normalize_path(abs_path).as_posix()
# Path() strips the trailing following symbols on windows, so we need to
# preserve it: ' ', '.'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we just use PurePosixPath instead of Path and special-casing Windows?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I could make this work, but it would continue to require special handling per OS due to calling .relative_to.

Since Windows considers uppercase and lowercase letters the same, on a windows device C:/home/michael and c:/home/michael are the same path. However, by using PurePosixPath, the comparison in relative_to uses Posix logic, which is that uppercase and lowercase letters are different. Due to this, .relative_to will not find relative matches that it should on windows.

This can be seen on all test cases when running on Windows. You end up getting the error:
ValueError: 'c:/home/michael/o.py' is not in the subpath of 'C:/home/michael' OR one path is relative and the other is absolute.

Another issue is that PurePosixPath does not like \\. It treats it as a normal path character (not a parts separator) and thus the path parts are not actually split up as expected.

I have not done a full test, as I don't wan't to invest more time until I know this is the route you want to go, but I believe a possible solution would be to lowercase the entire path inside _normalize_path if we are on a windows system, it may get us there, but I don't garuntee it.

Something like:

def _normalize_path(path: Union[str, Path]) -> PurePosixPath
    path = abspath(path).replace('\\', '/')
    if sys.platform.startswith('win'):
        path = path.lower()
    return PurePosixPath(path)

if sys.platform.startswith('win'):
rel_path += ' ' * _count_trailing_symbol(' ', abs_path)
rel_path += '.' * _count_trailing_symbol('.', abs_path)
# Path() strips the trailing slash, so we need to preserve it
# in case of directory-only negation
if self.negation and type(abs_path) == str and abs_path[-1] == '/':
Expand Down Expand Up @@ -218,3 +224,14 @@ def _normalize_path(path: Union[str, Path]) -> Path:
`Path.resolve()` does.
"""
return Path(abspath(path))


def _count_trailing_symbol(symbol: str, text: str) -> int:
"""Count the number of trailing characters in a string."""
count = 0
for char in reversed(str(text)):
if char == symbol:
count += 1
else:
break
return count
16 changes: 11 additions & 5 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from gitignore_parser import parse_gitignore

from unittest import TestCase, main
from unittest import TestCase, main, SkipTest


class Test(TestCase):
Expand Down Expand Up @@ -188,13 +188,19 @@ def test_symlink_to_another_directory(self):
This test ensures that the issue is now fixed.
"""
with TemporaryDirectory() as project_dir, TemporaryDirectory() as another_dir:
project_dir = Path(project_dir).resolve()
another_dir = Path(another_dir).resolve()
matches = _parse_gitignore_string('link', fake_base_dir=project_dir)

# Create a symlink to another directory.
link = Path(project_dir, 'link')
target = Path(another_dir, 'target')
link.symlink_to(target)

link = project_dir / 'link'
target = another_dir / 'target'

try:
link.symlink_to(target)
except OSError:
e = "Current user does not have permissions to perform symlink."
raise SkipTest(e)
# Check the intended behavior according to
# https://git-scm.com/docs/gitignore#_notes:
# Symbolic links are not followed and are matched as if they were regular
Expand Down