Skip to content

Commit be7adf8

Browse files
authored
Fixed RuntimeError when iterating git config entries with valueless keys. (#1457)
* Fixed RuntimeError when iterating git config entries with valueless keys. * Added unit tests. * Return Type Fixes for Valueless Config Entries The `get_multivar` method accepts any key name — there is no multivar marker in the git config format. A valueless key can therefore appear among multivar results, so `__next__` on `ConfigMultivarIterator` returns `str | None`. Valueless keys are also returned as `None` by `__getitem__`.
1 parent a44a305 commit be7adf8

File tree

2 files changed

+35
-6
lines changed

2 files changed

+35
-6
lines changed

pygit2/config.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def _next_entry(self) -> 'ConfigEntry':
7373

7474

7575
class ConfigMultivarIterator(ConfigIterator):
76-
def __next__(self) -> str: # type: ignore[override]
76+
def __next__(self) -> str | None: # type: ignore[override]
7777
entry = self._next_entry()
7878
return entry.value
7979

@@ -137,7 +137,7 @@ def __contains__(self, key: str | bytes) -> bool:
137137

138138
return True
139139

140-
def __getitem__(self, key: str | bytes) -> str:
140+
def __getitem__(self, key: str | bytes) -> str | None:
141141
"""
142142
When using the mapping interface, the value is returned as a string. In
143143
order to apply the git-config parsing rules, you can use
@@ -365,8 +365,8 @@ def raw_name(self) -> bytes:
365365
return ffi.string(self._entry.name)
366366

367367
@cached_property
368-
def raw_value(self) -> bytes:
369-
return ffi.string(self.c_value)
368+
def raw_value(self) -> bytes | None:
369+
return ffi.string(self.c_value) if self.c_value != ffi.NULL else None
370370

371371
@cached_property
372372
def level(self) -> int:
@@ -379,6 +379,6 @@ def name(self) -> str:
379379
return self.raw_name.decode('utf-8')
380380

381381
@property
382-
def value(self) -> str:
382+
def value(self) -> str | None:
383383
"""The entry's value as a string."""
384-
return self.raw_value.decode('utf-8')
384+
return self.raw_value.decode('utf-8') if self.raw_value is not None else None

test/test_config.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,35 @@ def test_iterator(config: Config) -> None:
185185
assert lst['core.bare']
186186

187187

188+
def test_valueless_key_iteration() -> None:
189+
# A valueless key (no `= value`) has a NULL value pointer in libgit2.
190+
# Iterating over such entries must not raise a RuntimeError.
191+
with open(CONFIG_FILENAME, 'w') as new_file:
192+
new_file.write('[section]\n\tvaluelesskey\n\tnormalkey = somevalue\n')
193+
194+
config = Config()
195+
config.add_file(CONFIG_FILENAME, 6)
196+
197+
entries = {entry.name: entry for entry in config}
198+
assert 'section.valuelesskey' in entries
199+
assert 'section.normalkey' in entries
200+
201+
202+
def test_valueless_key_value() -> None:
203+
# A valueless key must expose value=None and raw_value=None.
204+
with open(CONFIG_FILENAME, 'w') as new_file:
205+
new_file.write('[section]\n\tvaluelesskey\n\tnormalkey = somevalue\n')
206+
207+
config = Config()
208+
config.add_file(CONFIG_FILENAME, 6)
209+
210+
entries = {entry.name: entry for entry in config}
211+
assert entries['section.valuelesskey'].raw_value is None
212+
assert entries['section.valuelesskey'].value is None
213+
assert entries['section.normalkey'].raw_value == b'somevalue'
214+
assert entries['section.normalkey'].value == 'somevalue'
215+
216+
188217
def test_parsing() -> None:
189218
assert Config.parse_bool('on')
190219
assert Config.parse_bool('1')

0 commit comments

Comments
 (0)