diff --git a/git/util.py b/git/util.py index 5170592ab..9877ab8a2 100644 --- a/git/util.py +++ b/git/util.py @@ -1334,13 +1334,13 @@ def __getattr__(self, attr: str) -> T_IterableObj: def __getitem__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] self, index: Union[SupportsIndex, int, slice, str] ) -> T_IterableObj: - if isinstance(index, int): - return list.__getitem__(self, index) - elif isinstance(index, slice): + if isinstance(index, slice): raise ValueError("Index should be an int or str") + elif not isinstance(index, str): + return list.__getitem__(self, index) else: try: - return self.__getattr__(cast(str, index)) + return self.__getattr__(index) except AttributeError as e: raise IndexError(f"No item found with id {self._prefix}{index}") from e # END handle getattr diff --git a/test/test_util.py b/test/test_util.py index 0adbd0b2d..45c870777 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -5,7 +5,6 @@ import contextlib import errno -from datetime import datetime import os import pathlib import pickle @@ -13,6 +12,7 @@ import sys import threading import time +from datetime import datetime from unittest import SkipTest, mock import ddt @@ -43,8 +43,8 @@ rmtree, ) from test.cleanup import cleanup_directory -from test.lib.fixtures import writable_fixture from test.lib import TestBase, requires_symlinks, with_rw_repo +from test.lib.fixtures import writable_fixture @pytest.mark.parametrize( @@ -312,6 +312,45 @@ def test_altz_to_utctz_str_accepts_float_seconds(offset, expected): assert utctz_to_altz(result) == utctz_to_altz(expected) +@pytest.mark.parametrize("prefix", ["", "refs/"]) +@pytest.mark.parametrize("position", [0, -1]) +def test_iterable_list_accepts_index_protocol(prefix, position): + class Index: + def __index__(self): + return position + + values = IterableList("name", prefix) + members = [_Member(prefix + name) for name in ("one", "two")] + values.extend(members) + assert values[Index()] is members[position] + + +@pytest.mark.parametrize("position", [2, -3]) +def test_iterable_list_index_protocol_preserves_bounds_errors(position): + class Index: + def __index__(self): + return position + + values = IterableList("name") + values.extend([_Member("one"), _Member("two")]) + with pytest.raises(IndexError): + values[Index()] + + +def test_iterable_list_index_protocol_preserves_named_lookup_and_slice_rejection(): + values = IterableList("name", "refs/") + named = _Member("refs/0") + values.extend([named, _Member("refs/one")]) + assert values["0"] is named + assert values[False] is named + with pytest.raises(IndexError): + values["missing"] + with pytest.raises(ValueError): + values[:1] + with pytest.raises(TypeError): + values[0.0] + + @ddt.ddt class TestUtils(TestBase): """Tests for most utilities in :mod:`git.util`."""