| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 5714844 commit cdf3cff
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1103,6 +1103,46 @@ do not demonstrate a native Gix mismatch or eliminate any CLI calls. | |||
| 1103 | 1103 | | Config-backed remote/submodule maintenance | Missing config enumeration/multivar/removal operations (GIX-12), plus filesystem and worktree lifecycle requirements. | | |
| 1104 | 1104 | | 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. | | |
| 1105 | 1105 | ||
| 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 | + | ||
| 1106 | 1146 | ## Maintaining the adapters | |
| 1107 | 1147 | ||
| 1108 | 1148 | `git/_backend.py` contains the capability checks. Managed command adapters return | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -51,6 +51,10 @@ Git executable and safety | |||
| 51 | 51 | API changes | |
| 52 | 52 | ----------- | |
| 53 | 53 | ||
| 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. | ||
| 54 | 58 | * Explicit ``from git import Union`` imports remain available for Demisto SDK's | |
| 55 | 59 | validator registration, retaining the historical ``typing.Union`` alias. | |
| 56 | 60 | * Editable/source imports expose the release version in ``git.__version__``, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,6 +14,7 @@ | |||
| 14 | 14 | import sys | |
| 15 | 15 | import tempfile | |
| 16 | 16 | from contextlib import contextmanager | |
| 17 | + from collections.abc import Mapping | ||
| 17 | 18 | from typing import Any, Dict, Generic, Iterator, List, OrderedDict, Sequence, Tuple, TypeVar, Union, TYPE_CHECKING | |
| 18 | 19 | ||
| 19 | 20 | from git.compat import defenc, force_text | |
@@ -491,6 +492,22 @@ def set_value(self, section: str, option: str, value: Any) -> "GitConfigParser": | |||
| 491 | 492 | self._mutate("set", "--all", "--", key, self._value(value)) | |
| 492 | 493 | return self | |
| 493 | 494 | ||
| 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 | + | ||
| 494 | 511 | def add_value(self, section: str, option: str, value: Any) -> "GitConfigParser": | |
| 495 | 512 | key = self._key(section, option) | |
| 496 | 513 | self._mutate("set", "--append", "--", key, self._value(value)) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,6 +22,30 @@ def test_legacy_typing_union_import_supports_downstream_registration(): | |||
| 22 | 22 | assert GitUnion is Union | |
| 23 | 23 | ||
| 24 | 24 | ||
| 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 | + | ||
| 25 | 49 | @pytest.mark.parametrize("object_format", ["sha1", "sha256"]) | |
| 26 | 50 | @pytest.mark.parametrize("ref_format", ["files", "reftable"]) | |
| 27 | 51 | def test_repo_accepts_explicit_literal_path_selection(object_format, ref_format, monkeypatch): | |
| Back | FazBrowse Home | New Git URL |
0 commit comments