Skip to content

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

Open
vruyr wants to merge 1 commit intolibgit2:masterfrom
vruyr:master
Open

Fixed RuntimeError when iterating git config entries with valueless keys.#1457
vruyr wants to merge 1 commit intolibgit2:masterfrom
vruyr:master

Conversation

@vruyr
Copy link

@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>

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

1 participant