FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(config): restore nonempty section mapping assignment · gitpython-developers/GitPython@cdf3cff · GitHub

Repository navigation

Commit cdf3cff

Browse files
authored andcommitted
fix(config): restore nonempty section mapping assignment
Kas 5.5 temporarily changes Git identity and restores it with `config["user"] = original_values`. The migrated parser removed inherited mapping assignment, leaving cleanup broken although released GitPython 3.2.1 passes that exact caller check. Validate the complete nonempty mapping before changing the file, then use existing Git config section removal and value writes. This replaces duplicate and obsolete options while retaining other sections. Each Git mutation locks independently; replacement is not transaction-wide atomic. Empty sections and `add_section()` remain unsupported by the available native configuration APIs. Kas's three SSH validation failures are retained and documented alongside the existing Airflow mirror-fetch adapter gap. No Git serialization or successful empty-section result is implemented in Python. All four new SHA-1/SHA-256, files/reftable regressions failed before the fix. All 90 config/downstream cases pass on CLI and Gix; Ruff lint/format, mypy for `git/config.py`, and `git diff --check` pass.
1 parent 5714844 commit cdf3cff

4 files changed

Lines changed: 85 additions & 0 deletions

File tree

‎doc/gix-backend.md‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,46 @@ do not demonstrate a native Gix mismatch or eliminate any CLI calls.
11031103
| Config-backed remote/submodule maintenance | Missing config enumeration/multivar/removal operations (GIX-12), plus filesystem and worktree lifecycle requirements. |
11041104
| Hook execution | Every explicit hook request, including an absent hook, retains Git-managed lookup, execution and diagnostics until GIX-23 is addressed. Native operations retain GitPython's default hook/fsmonitor/maintenance restrictions. |
11051105

1106+
### Downstream mirror-fetch result gap
1107+
1108+
Airflow Git provider 1.0.1's bare-cache refspecs
1109+
`+refs/heads/*:refs/heads/*` and `+refs/tags/*:refs/tags/*` expose an existing
1110+
GitPython result-decoding problem when a branch and tag share a name. With Git
1111+
2.53.0, both refs update successfully, but verbose output abbreviates their
1112+
names. The local CLI and Gix selections omit the ambiguous branch result;
1113+
released GitPython 3.2.1 instead returns a nonexistent `refs/remotes/main`
1114+
reference whose commit cannot be read. The same problem occurs with
1115+
`+refs/*:refs/*`. Two preserved real-repository supplemental tests fail with
1116+
all three selections; Airflow's 175 unchanged upstream tests pass.
1117+
1118+
This is a demonstrated GitPython adapter gap, not a demonstrated Gitoxide
1119+
mismatch. Git's `fetch --porcelain` reports full destination refnames and old/new
1120+
IDs, providing native evidence for a future adapter correction. Fetch remains
1121+
a CLI fallback: GixPython 0.2.0 must expose per-ref identities, status and notes
1122+
before its fetch outcome can satisfy `FetchInfo`. Do not infer namespaces or
1123+
reimplement fetch semantics in Python. Reproduction and baseline evidence are
1124+
retained in the [third downstream audit](../test/downstream/audit-2026-10-22-batch-3.md).
1125+
1126+
### Downstream empty configuration sections
1127+
1128+
Kas 5.5's three unchanged SSH key validation tests call
1129+
`GitConfigParser.add_section('gpg "ssh"')` before assigning
1130+
`allowedSignersFile` through a section proxy. They pass with released GitPython
1131+
3.2.1 and fail with both local backends because empty sections and the inherited
1132+
ConfigParser section API were removed. Git 2.53.0's config mutation interface
1133+
and GixPython 0.2.0 do not expose creating an empty section. This is a missing
1134+
capability and an intentional, demonstrated GitPython API break; it is not a
1135+
demonstrated Gitoxide mismatch. No successful empty-section result is invented
1136+
or serialized in Python. GixPython needs a supported native configuration edit
1137+
that creates sections before this contract can be restored.
1138+
1139+
Kas's separate identity cleanup uses nonempty section assignment, which is
1140+
restored through existing Git config remove/set operations under GIX-12. Values
1141+
are validated before mutation; individual Git operations lock independently,
1142+
so replacement does not claim transaction-wide atomicity. Downstream can avoid
1143+
the empty-section API by calling `set_value('gpg "ssh"', 'allowedSignersFile',
1144+
path)` directly. The three upstream failures remain in the audit.
1145+
11061146
## Maintaining the adapters
11071147

11081148
`git/_backend.py` contains the capability checks. Managed command adapters return

‎doc/source/changes.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ Git executable and safety
5151
API changes
5252
-----------
5353

54+
* ``GitConfigParser[section] = values`` again replaces a nonempty section,
55+
restoring Kas's Git identity cleanup. Git performs each mutation and locks
56+
it independently; replacement is not one atomic transaction. Empty sections
57+
and ``add_section()`` remain unsupported, including Kas's SSH key setup.
5458
* Explicit ``from git import Union`` imports remain available for Demisto SDK's
5559
validator registration, retaining the historical ``typing.Union`` alias.
5660
* Editable/source imports expose the release version in ``git.__version__``,

‎git/config.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import sys
1515
import tempfile
1616
from contextlib import contextmanager
17+
from collections.abc import Mapping
1718
from typing import Any, Dict, Generic, Iterator, List, OrderedDict, Sequence, Tuple, TypeVar, Union, TYPE_CHECKING
1819

1920
from git.compat import defenc, force_text
@@ -491,6 +492,22 @@ def set_value(self, section: str, option: str, value: Any) -> "GitConfigParser":
491492
self._mutate("set", "--all", "--", key, self._value(value))
492493
return self
493494

495+
def __setitem__(self, section: str, values: Mapping[str, Any]) -> None:
496+
"""Replace a nonempty section through Git's existing config mutations.
497+
498+
Each value is validated before modifying the file. Git locks individual
499+
mutations; section replacement does not reserve a transaction-wide lock.
500+
"""
501+
self._assure_writable("section assignment")
502+
if not isinstance(values, Mapping):
503+
raise TypeError("Git configuration section values must be a mapping")
504+
if not values:
505+
raise ValueError("Empty Git configuration sections are unsupported")
506+
validated = [(self._key(section, option), self._value(value)) for option, value in values.items()]
507+
self.remove_section(section)
508+
for key, value in validated:
509+
self._mutate("set", "--all", "--", key, value)
510+
494511
def add_value(self, section: str, option: str, value: Any) -> "GitConfigParser":
495512
key = self._key(section, option)
496513
self._mutate("set", "--append", "--", key, self._value(value))

‎test/test_downstream_compatibility.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,30 @@ def test_legacy_typing_union_import_supports_downstream_registration():
2222
assert GitUnion is Union
2323

2424

25+
@pytest.mark.parametrize("object_format", ["sha1", "sha256"])
26+
@pytest.mark.parametrize("ref_format", ["files", "reftable"])
27+
def test_config_section_assignment_restores_downstream_git_identity(object_format, ref_format):
28+
with fixture_repo("downstream-index.sh", object_format, ref_format, write=True) as repo:
29+
with repo.config_writer() as config:
30+
config.set_value("user", "name", "Temporary identity")
31+
config.set_value("user", "email", "temporary@example.invalid")
32+
config.add_value("user", "email", "duplicate@example.invalid")
33+
config.set_value("user", "signingkey", "temporary-key")
34+
config.set_value("core", "editor", "retained")
35+
config["user"] = {"name": "Original Person", "email": "original@example.invalid"}
36+
assert config.get("user", "name") == "Original Person"
37+
assert config.get_values("user", "email") == ["original@example.invalid"]
38+
assert not config.has_option("user", "signingkey")
39+
assert config.get("core", "editor") == "retained"
40+
with pytest.raises(ValueError, match="valueless"):
41+
config["user"] = {"name": "Discarded", "email": None}
42+
assert config.get("user", "name") == "Original Person"
43+
with pytest.raises(ValueError, match="Empty"):
44+
config["user"] = {}
45+
assert repo.git.config("get", "user.name") == "Original Person"
46+
assert repo.git.config("get", "user.email") == "original@example.invalid"
47+
48+
2549
@pytest.mark.parametrize("object_format", ["sha1", "sha256"])
2650
@pytest.mark.parametrize("ref_format", ["files", "reftable"])
2751
def test_repo_accepts_explicit_literal_path_selection(object_format, ref_format, monkeypatch):

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL