Skip to content

Commit 0cfaa00

Browse files
gavenrenByron
authored andcommitted
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 ec12459 commit 0cfaa00

2 files changed

Lines changed: 44 additions & 5 deletions

File tree

‎git/util.py‎

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

‎test/test_util.py‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55

66
import contextlib
77
import errno
8-
from datetime import datetime
98
import os
109
import pathlib
1110
import pickle
1211
import stat
1312
import sys
1413
import threading
1514
import time
15+
from datetime import datetime
1616
from unittest import SkipTest, mock
1717

1818
import ddt
@@ -43,8 +43,8 @@
4343
rmtree,
4444
)
4545
from test.cleanup import cleanup_directory
46-
from test.lib.fixtures import writable_fixture
4746
from test.lib import TestBase, requires_symlinks, with_rw_repo
47+
from test.lib.fixtures import writable_fixture
4848

4949

5050
@pytest.mark.parametrize(
@@ -312,6 +312,45 @@ def test_altz_to_utctz_str_accepts_float_seconds(offset, expected):
312312
assert utctz_to_altz(result) == utctz_to_altz(expected)
313313

314314

315+
@pytest.mark.parametrize("prefix", ["", "refs/"])
316+
@pytest.mark.parametrize("position", [0, -1])
317+
def test_iterable_list_accepts_index_protocol(prefix, position):
318+
class Index:
319+
def __index__(self):
320+
return position
321+
322+
values = IterableList("name", prefix)
323+
members = [_Member(prefix + name) for name in ("one", "two")]
324+
values.extend(members)
325+
assert values[Index()] is members[position]
326+
327+
328+
@pytest.mark.parametrize("position", [2, -3])
329+
def test_iterable_list_index_protocol_preserves_bounds_errors(position):
330+
class Index:
331+
def __index__(self):
332+
return position
333+
334+
values = IterableList("name")
335+
values.extend([_Member("one"), _Member("two")])
336+
with pytest.raises(IndexError):
337+
values[Index()]
338+
339+
340+
def test_iterable_list_index_protocol_preserves_named_lookup_and_slice_rejection():
341+
values = IterableList("name", "refs/")
342+
named = _Member("refs/0")
343+
values.extend([named, _Member("refs/one")])
344+
assert values["0"] is named
345+
assert values[False] is named
346+
with pytest.raises(IndexError):
347+
values["missing"]
348+
with pytest.raises(ValueError):
349+
values[:1]
350+
with pytest.raises(TypeError):
351+
values[0.0]
352+
353+
315354
@ddt.ddt
316355
class TestUtils(TestBase):
317356
"""Tests for most utilities in :mod:`git.util`."""

0 commit comments

Comments
 (0)