diff --git a/pygit2/config.py b/pygit2/config.py index f05a8d13..5ffb6896 100644 --- a/pygit2/config.py +++ b/pygit2/config.py @@ -73,7 +73,7 @@ def _next_entry(self) -> 'ConfigEntry': class ConfigMultivarIterator(ConfigIterator): - def __next__(self) -> str: # type: ignore[override] + def __next__(self) -> str | None: # type: ignore[override] entry = self._next_entry() return entry.value @@ -137,7 +137,7 @@ def __contains__(self, key: str | bytes) -> bool: return True - def __getitem__(self, key: str | bytes) -> str: + def __getitem__(self, key: str | bytes) -> str | None: """ When using the mapping interface, the value is returned as a string. In order to apply the git-config parsing rules, you can use @@ -365,8 +365,8 @@ def raw_name(self) -> bytes: return ffi.string(self._entry.name) @cached_property - def raw_value(self) -> bytes: - return ffi.string(self.c_value) + def raw_value(self) -> bytes | None: + return ffi.string(self.c_value) if self.c_value != ffi.NULL else None @cached_property def level(self) -> int: @@ -379,6 +379,6 @@ def name(self) -> str: return self.raw_name.decode('utf-8') @property - def value(self) -> str: + def value(self) -> str | None: """The entry's value as a string.""" - return self.raw_value.decode('utf-8') + return self.raw_value.decode('utf-8') if self.raw_value is not None else None diff --git a/test/test_config.py b/test/test_config.py index 247d55c5..2d1a2a11 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -185,6 +185,35 @@ def test_iterator(config: Config) -> None: assert lst['core.bare'] +def test_valueless_key_iteration() -> None: + # A valueless key (no `= value`) has a NULL value pointer in libgit2. + # Iterating over such entries must not raise a RuntimeError. + with open(CONFIG_FILENAME, 'w') as new_file: + new_file.write('[section]\n\tvaluelesskey\n\tnormalkey = somevalue\n') + + config = Config() + config.add_file(CONFIG_FILENAME, 6) + + entries = {entry.name: entry for entry in config} + assert 'section.valuelesskey' in entries + assert 'section.normalkey' in entries + + +def test_valueless_key_value() -> None: + # A valueless key must expose value=None and raw_value=None. + with open(CONFIG_FILENAME, 'w') as new_file: + new_file.write('[section]\n\tvaluelesskey\n\tnormalkey = somevalue\n') + + config = Config() + config.add_file(CONFIG_FILENAME, 6) + + entries = {entry.name: entry for entry in config} + assert entries['section.valuelesskey'].raw_value is None + assert entries['section.valuelesskey'].value is None + assert entries['section.normalkey'].raw_value == b'somevalue' + assert entries['section.normalkey'].value == 'somevalue' + + def test_parsing() -> None: assert Config.parse_bool('on') assert Config.parse_bool('1')