Skip to content

Commit c440559

Browse files
committed
fix: support index protocol in IterableList lookups
`IterableList.__getitem__` advertises `SupportsIndex`, but routes every non-`int` index through named lookup. Valid objects implementing `__index__` therefore raise a string-concatenation `TypeError` instead of selecting an element. Delegate non-string, non-slice indices to the built-in list implementation, which already handles the index protocol and bounds checks. Keep named lookup and explicit slice rejection unchanged. Six protocol/bounds regressions fail before this fix; a lookup/slice control passes. Utility and typing modules pass with both CLI and Gix backends (128 passed, 39 skipped each). Applicable pre-commit checks pass. Assisted-by: OpenAI Codex (model: unknown, autonomous)
1 parent cfb222f commit c440559

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

‎git/util.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,10 +1333,10 @@ def __getattr__(self, attr: str) -> T_IterableObj:
13331333
def __getitem__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
13341334
self, index: Union[SupportsIndex, int, slice, str]
13351335
) -> T_IterableObj:
1336-
if isinstance(index, int):
1337-
return list.__getitem__(self, index)
1338-
elif isinstance(index, slice):
1336+
if isinstance(index, slice):
13391337
raise ValueError("Index should be an int or str")
1338+
elif not isinstance(index, str):
1339+
return list.__getitem__(self, index)
13401340
else:
13411341
try:
13421342
return self.__getattr__(cast(str, index))

‎test/test_util.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,45 @@ def __repr__(self):
280280
return f"{type(self).__name__}({self.name!r})"
281281

282282

283+
@pytest.mark.parametrize("prefix", ["", "refs/"])
284+
@pytest.mark.parametrize("position", [0, -1])
285+
def test_iterable_list_accepts_index_protocol(prefix, position):
286+
class Index:
287+
def __index__(self):
288+
return position
289+
290+
values = IterableList("name", prefix)
291+
members = [_Member(prefix + name) for name in ("one", "two")]
292+
values.extend(members)
293+
assert values[Index()] is members[position]
294+
295+
296+
@pytest.mark.parametrize("position", [2, -3])
297+
def test_iterable_list_index_protocol_preserves_bounds_errors(position):
298+
class Index:
299+
def __index__(self):
300+
return position
301+
302+
values = IterableList("name")
303+
values.extend([_Member("one"), _Member("two")])
304+
with pytest.raises(IndexError):
305+
values[Index()]
306+
307+
308+
def test_iterable_list_index_protocol_preserves_named_lookup_and_slice_rejection():
309+
values = IterableList("name", "refs/")
310+
named = _Member("refs/0")
311+
values.extend([named, _Member("refs/one")])
312+
assert values["0"] is named
313+
assert values[False] is named
314+
with pytest.raises(IndexError):
315+
values["missing"]
316+
with pytest.raises(ValueError):
317+
values[:1]
318+
with pytest.raises(TypeError):
319+
values[0.0]
320+
321+
283322
@ddt.ddt
284323
class TestUtils(TestBase):
285324
"""Tests for most utilities in :mod:`git.util`."""

0 commit comments

Comments
 (0)