From 6405c45951060b28d97253c65d90f1a482dd6801 Mon Sep 17 00:00:00 2001 From: Vruyr Gyolchanyan Date: Mon, 23 Mar 2026 07:19:31 -0400 Subject: [PATCH 1/3] Fixed RuntimeError when iterating git config entries with valueless keys. --- pygit2/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pygit2/config.py b/pygit2/config.py index f05a8d13..9a966674 100644 --- a/pygit2/config.py +++ b/pygit2/config.py @@ -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 From a667f1ff90133d7f0f7b1bfdc32b8d04f229d896 Mon Sep 17 00:00:00 2001 From: Vruyr Gyolchanyan Date: Tue, 24 Mar 2026 06:44:14 -0400 Subject: [PATCH 2/3] Added unit tests. --- test/test_config.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) 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') From f150e09ad3f90fbc91346be2c30c421c2d4e5798 Mon Sep 17 00:00:00 2001 From: Vruyr Gyolchanyan Date: Wed, 25 Mar 2026 07:25:48 -0400 Subject: [PATCH 3/3] Return Type Fixes for Valueless Config Entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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__`. --- pygit2/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygit2/config.py b/pygit2/config.py index 9a966674..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