Prechádzať zdrojové kódy

Config.set replaces values by default, Config.add appends them. Fixes #1545

Jelmer Vernooij 2 týždňov pred
rodič
commit
f123d3d506
3 zmenil súbory, kde vykonal 66 pridanie a 2 odobranie
  1. 3 0
      NEWS
  2. 28 0
      dulwich/config.py
  3. 35 2
      tests/test_config.py

+ 3 - 0
NEWS

@@ -7,6 +7,9 @@
 
  * Fix wheels workflow. (Jelmer Vernooij)
 
+ * ``Config.set`` replaces values by default, ``Config.add``
+   appends them. (Jelmer Vernooij, #1545)
+
 0.22.8	2025-03-02
 
  * Allow passing in plain strings to ``dulwich.porcelain.tag_create``

+ 28 - 0
dulwich/config.py

@@ -96,6 +96,13 @@ class CaseInsensitiveOrderedMultiDict(MutableMapping):
         self._real.append((key, value))
         self._keyed[lower_key(key)] = value
 
+    def set(self, key, value) -> None:
+        # This method replaces all existing values for the key
+        lower = lower_key(key)
+        self._real = [(k, v) for k, v in self._real if lower_key(k) != lower]
+        self._real.append((key, value))
+        self._keyed[lower] = value
+
     def __delitem__(self, key) -> None:
         key = lower_key(key)
         del self._keyed[key]
@@ -340,6 +347,27 @@ class ConfigDict(Config, MutableMapping[Section, MutableMapping[Name, Value]]):
     ) -> None:
         section, name = self._check_section_and_name(section, name)
 
+        if isinstance(value, bool):
+            value = b"true" if value else b"false"
+
+        if not isinstance(value, bytes):
+            value = value.encode(self.encoding)
+
+        section_dict = self._values.setdefault(section)
+        if hasattr(section_dict, "set"):
+            section_dict.set(name, value)
+        else:
+            section_dict[name] = value
+
+    def add(
+        self,
+        section: SectionLike,
+        name: NameLike,
+        value: Union[ValueLike, bool],
+    ) -> None:
+        """Add a value to a configuration setting, creating a multivar if needed."""
+        section, name = self._check_section_and_name(section, name)
+
         if isinstance(value, bool):
             value = b"true" if value else b"false"
 

+ 35 - 2
tests/test_config.py

@@ -315,6 +315,20 @@ class ConfigDictTests(TestCase):
 
         self.assertEqual([(b"core2",)], list(cd.sections()))
 
+    def test_set_vs_add(self) -> None:
+        cd = ConfigDict()
+        # Test add() creates multivars
+        cd.add((b"core",), b"foo", b"value1")
+        cd.add((b"core",), b"foo", b"value2")
+        self.assertEqual(
+            [b"value1", b"value2"], list(cd.get_multivar((b"core",), b"foo"))
+        )
+
+        # Test set() replaces values
+        cd.set((b"core",), b"foo", b"value3")
+        self.assertEqual([b"value3"], list(cd.get_multivar((b"core",), b"foo")))
+        self.assertEqual(b"value3", cd.get((b"core",), b"foo"))
+
 
 class StackedConfigTests(TestCase):
     def test_default_backends(self) -> None:
@@ -491,8 +505,8 @@ class ApplyInsteadOfTests(TestCase):
 
     def test_apply_multiple(self) -> None:
         config = ConfigDict()
-        config.set(("url", "https://samba.org/"), "insteadOf", "https://blah.com/")
-        config.set(("url", "https://samba.org/"), "insteadOf", "https://example.com/")
+        config.add(("url", "https://samba.org/"), "insteadOf", "https://blah.com/")
+        config.add(("url", "https://samba.org/"), "insteadOf", "https://example.com/")
         self.assertEqual(
             [b"https://blah.com/", b"https://example.com/"],
             list(config.get_multivar(("url", "https://samba.org/"), "insteadOf")),
@@ -618,3 +632,22 @@ class CaseInsensitiveConfigTests(TestCase):
         config[("branch", "master")] = "value"
         self.assertEqual("value", config[("BRANCH", "MASTER")])
         self.assertEqual("value", config[("Branch", "Master")])
+
+
+class ConfigFileSetTests(TestCase):
+    def test_set_replaces_value(self) -> None:
+        # Test that set() replaces the value instead of appending
+        cf = ConfigFile()
+        cf.set((b"core",), b"sshCommand", b"ssh -i ~/.ssh/id_rsa1")
+        cf.set((b"core",), b"sshCommand", b"ssh -i ~/.ssh/id_rsa2")
+
+        # Should only have one value
+        self.assertEqual(b"ssh -i ~/.ssh/id_rsa2", cf.get((b"core",), b"sshCommand"))
+
+        # When written to file, should only have one entry
+        f = BytesIO()
+        cf.write_to_file(f)
+        content = f.getvalue()
+        self.assertEqual(1, content.count(b"sshCommand"))
+        self.assertIn(b"sshCommand = ssh -i ~/.ssh/id_rsa2", content)
+        self.assertNotIn(b"id_rsa1", content)