Skip to content

RepositoryConfig.add_file: TypeError introduced in v1.21 might be too zealous #1508

Open

Description

@jorio

In pygit2 1.21, RepositoryConfig.add_file overrides Config.add_file to raise TypeError.

This new TypeError blocks the following real-world scenario that used to be possible in previous versions:

Say there's no XDG config file on the system when you create a Repository object.
If your application then creates ~/.config/git/config, the existing repo.config won't pick it up.
Pre-1.21, you used to be able to call repo.config.add_file(path, ConfigLevel.XDG, force=False) to solve this.

libgit2 itself doesn't prevent this, and it works fine in this scenario, so I believe this new restriction is artificial.

The only other alternative, as far as I know, would be to recreate the Repository object from scratch; or, to rewire RepositoryConfig.add_file to Config.add_file after importing libgit2. But neither of those solutions are really satisfactory.

What are your thoughts on removing this TypeError?

Activity

  1. jdavid commented on Nov 6, 2026

    @jdavid
    Member

    Kimi's review...


    In pygit2 1.21, RepositoryConfig.add_file raises TypeError, which blocks re-adding an XDG config file (~/.config/git/config) to an already-open repo.config when that file is created after the Repository is opened. The reporter says libgit2 itself allows this, so the restriction is artificial and should be removed.

    What I verified

    The facts are accurate. Pre-1.21, repo.config returned a plain Config whose add_file called git_config_add_file_ondisk directly, so the scenario worked. The TypeError was added in commit 9a4694a (PR #1501, "Refactor Config to extract DefaultConfig and RepositoryConfig subclasses"), released in 1.21, as groundwork for PR #1473's planned context-manager config overrides. It's a pure Python-layer override (pygit2/config.py:907-918), and libgit2 1.9.7 does accept the call — I confirmed the add succeeds and reads see the new values.

    But the restriction isn't purely artificial — it guards a real footgun I reproduced. After hot-adding a lower-level file (XDG, level 3) to a repository config, all subsequent writes through that object silently redirect from .git/config to the added file, contradicting RepositoryConfig's documented contract that "when a write operation occurs, only the local configuration is changed" (pygit2/config.py:825-826). Root cause in libgit2:

    • git_repository_config pins write targeting via git_config_set_writeorder(cfg, [LOCAL], 1), giving local write_order = 0 and every other backend -1 (ci/libgit2-1.9.7/src/libgit2/repository.c:1342-1344).
    • git_config__add_instance assigns any newly added backend write_order = level (config.c:308), and writers are sorted by write order descending — so the added XDG backend (3) outranks local (0) and becomes the sole write target.

    This hazard existed pre-1.21 too, but the 1.21 refactor drew an explicit semantic boundary (RepositoryConfig = effective config with local as the only write target), and the override enforces it.

    Also relevant to the reporter's workaround: libgit2 caches the opened config on the repository object (repo->_config, repository.c:1440), so re-accessing the repo.config property does not pick up a newly created XDG file — I verified a fresh repo.config still misses the new values. Recreating the Repository object genuinely is the only clean workaround today, as the reporter suspected.

    Assessment

    The use case is legitimate and libgit2 permits the operation, but simply deleting the TypeError would expose users to the silent write redirect. Reasonable resolutions:

    1. Allow add_file on RepositoryConfig but re-pin the write order afterward — git_config_set_writeorder is public API in libgit2 1.9 (include/git2/config.h:343), so pygit2 could call it with [ConfigLevel.LOCAL] after a successful add. This restores pre-1.21 capability while fixing the write-redirect bug that pre-1.21 had.
    2. Keep the TypeError and add a purpose-built reload API (e.g., re-open a given level's file), implemented with the same write-order fix.
    3. Treat the write redirect as a libgit2 bug and pursue it upstream — a backend added to a write-order-pinned config arguably shouldn't become the default writer.

    Note also that libgit2's config docs steer custom additions toward GIT_CONFIG_LEVEL_APP (include/git2/config.h:36-41), which is the design PR #1473's context-manager overrides will use — worth considering how that interacts with any add_file relaxation.

  2. jdavid commented on Nov 6, 2026

    @jdavid
    Member
  3. beamerblvd commented on Nov 11, 2026

    @beamerblvd
    Contributor

    Sorry, this slipped through my inbox...

    @jorio is correct that this is a valid use case, but I share the concerns that Kimi outlined. I think what needs to happen here is:

    RepositoryConfig still overrides add_file, but instead of always raising TypeError, it:

    1. Enforces that level < ConfigLevel.LOCAL (or raises a ValueError) and then
    2. Uses git_config_set_writeorder to re-pin the write order to the correct default write order.

    I (or Kimi) would need to analyze it, but I suspect we may also need to do the same for DefaultConfig.add_file, which also currently always raises TypeError.

    It's worth noting that pull request #1503 already puts in place the necessary apparatus to invoke git_config_set_writeorder and also defines the correct default write order. Neither of these is the case in the current master. It would be much easier to wait and fix this once #1503 is merged, though that might not be desirable. If it matters, fixing this before merging #1503 will cause merge conflicts in #1503 that will have to be resolved.

  4. jorio commented on Nov 12, 2026

    @jorio
    ContributorAuthor

    It would be much easier to wait and fix this once #1503 is merged

    That sounds sensible! As far as this issue is concerned, in existing application code, doing RepositoryConfig.add_file = Config.add_file after importing pygit2 restores the pre-1.21 behavior, so I don't mind waiting. Thank you for your work on the config!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions