Skip to content

Fixed RuntimeError when iterating git config entries with valueless keys.#1457

Merged
jdavid merged 3 commits intolibgit2:masterfrom
vruyr:master
Mar 25, 2026
Merged

Fixed RuntimeError when iterating git config entries with valueless keys.#1457
jdavid merged 3 commits intolibgit2:masterfrom
vruyr:master

Conversation

@vruyr
Copy link
Contributor

@vruyr vruyr commented Mar 23, 2026

Fixes #1456

Description

Iterating over repo.config raises a RuntimeError if the configuration (including any include.path-sourced files) contains a valueless key — a boolean key written without =, e.g.:

[some-section "identifier"]
    booleanflag

Traceback

Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
  File ".../pygit2/config.py", line 65, in __next__
    return self._next_entry()
           ~~~~~~~~~~~~~~~~^^
  File ".../pygit2/config.py", line 72, in _next_entry
    return ConfigEntry._from_c(centry[0], self)
           ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File ".../pygit2/config.py", line 349, in _from_c
    entry.raw_value = entry.raw_value
                      ^^^^^^^^^^^^^^^
  File ".../functools.py", line 1126, in __get__
    val = self.func(instance)
  File ".../pygit2/config.py", line 369, in raw_value
    return ffi.string(self.c_value)
           ~~~~~~~~~~^^^^^^^^^^^^^^
RuntimeError: cannot use string() on <cdata 'char *' NULL>

Root Cause

libgit2 deliberately sets git_config_entry.value = NULL for valueless keys. The entry struct is zero-initialized via git__calloc, and the value field is only populated when a value is actually present (src/libgit2/config_file.c):

entry = git__calloc(1, sizeof(git_config_list_entry));
/* ... */
if (var_value) {
    entry->base.entry.value = git__strdup(var_value);
    GIT_ERROR_CHECK_ALLOC(entry->base.entry.value);
}

libgit2's own code handles the NULL case explicitly (same file):

else if ((!existing->base.entry.value && !value) ||
         (existing->base.entry.value && value &&
          !strcmp(existing->base.entry.value, value)))

So NULL in git_config_entry.value is intentional and documented behavior.

pygit2's _from_c eagerly caches raw_value during iteration (the workaround introduced for #970):

if iterator is not None:
    entry.raw_name = entry.raw_name
    entry.raw_value = entry.raw_value   # ← crashes when c_value is NULL
    entry.level = entry.level

And raw_value has no NULL guard:

@cached_property
def raw_value(self) -> bytes:
    return ffi.string(self.c_value)   # RuntimeError if c_value is NULL

Expected Behavior

Valueless keys should be represented with value = None (or raw_value = None) rather than raising an exception.

Reproduction

$ mkdir /tmp/testrepo && cd /tmp/testrepo && git init
Initialized empty Git repository in /tmp/testrepo/.git/

$ printf '[mysection]\n\tbooleanflag\n' >> .git/config

$ cat .git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[mysection]
        booleanflag

$ uv run --with pygit2==1.19.1 python - <<EOF
import pygit2
repo = pygit2.Repository('/tmp/testrepo')
for entry in repo.config:
    print(entry.name, entry.value)
EOF
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
  File ".../pygit2/config.py", line 65, in __next__
    return self._next_entry()
           ~~~~~~~~~~~~~~~~^^
  File ".../pygit2/config.py", line 72, in _next_entry
    return ConfigEntry._from_c(centry[0], self)
           ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File ".../pygit2/config.py", line 349, in _from_c
    entry.raw_value = entry.raw_value
                      ^^^^^^^^^^^^^^^
  File ".../functools.py", line 1126, in __get__
    val = self.func(instance)
  File ".../pygit2/config.py", line 369, in raw_value
    return ffi.string(self.c_value)
           ~~~~~~~~~~^^^^^^^^^^^^^^
RuntimeError: cannot use string() on <cdata 'char *' NULL>

@jdavid
Copy link
Member

jdavid commented Mar 24, 2026

Thanks @vruyr , could you add a unit test?

@vruyr
Copy link
Contributor Author

vruyr commented Mar 24, 2026

Done

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__`.
@jdavid jdavid merged commit be7adf8 into libgit2:master Mar 25, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RuntimeError: cannot use string() on NULL when iterating config with valueless (boolean) keys

2 participants