Просмотр исходного кода

Fix handling of casing of subsection names in config (#1556)

Fixes #1521
Jelmer Vernooij 1 неделя назад
Родитель
Сommit
bc22bab223
3 измененных файлов с 49 добавлено и 6 удалено
  1. 4 1
      NEWS
  2. 5 3
      dulwich/config.py
  3. 40 2
      tests/test_config.py

+ 4 - 1
NEWS

@@ -24,9 +24,12 @@
 
  * Add basic merge command. (Jelmer Vernooij)
 
-* Fix typing for ``dulwich.client`` methods that take repositories.
+ * Fix typing for ``dulwich.client`` methods that take repositories.
    (Jelmer Vernooij, #1521)
 
+ * Fix handling of casing of subsection names in config.
+   (Jelmer Vernooij, #1183)
+
 0.22.8	2025-03-02
 
  * Allow passing in plain strings to ``dulwich.porcelain.tag_create``

+ 5 - 3
dulwich/config.py

@@ -23,8 +23,6 @@
 
 Todo:
  * preserve formatting when updating configuration files
- * treat subsection names as case-insensitive for [branch.foo] style
-   subsections
 """
 
 import os
@@ -49,7 +47,11 @@ def lower_key(key):
         return key.lower()
 
     if isinstance(key, Iterable):
-        return type(key)(map(lower_key, key))  # type: ignore
+        # For config sections, only lowercase the section name (first element)
+        # but preserve the case of subsection names (remaining elements)
+        if len(key) > 0:
+            return (key[0].lower(),) + key[1:]
+        return key
 
     return key
 

+ 40 - 2
tests/test_config.py

@@ -515,6 +515,21 @@ class ApplyInsteadOfTests(TestCase):
             "https://samba.org/", apply_instead_of(config, "https://example.com/")
         )
 
+    def test_apply_preserves_case_in_subsection(self) -> None:
+        """Test that mixed-case URLs (like those with access tokens) are preserved."""
+        config = ConfigDict()
+        # GitHub access tokens have mixed case that must be preserved
+        url_with_token = "https://ghp_AbCdEfGhIjKlMnOpQrStUvWxYz1234567890@github.com/"
+        config.set(("url", url_with_token), "insteadOf", "https://github.com/")
+
+        # Apply the substitution
+        result = apply_instead_of(config, "https://github.com/jelmer/dulwich.git")
+        expected = "https://ghp_AbCdEfGhIjKlMnOpQrStUvWxYz1234567890@github.com/jelmer/dulwich.git"
+        self.assertEqual(expected, result)
+
+        # Verify the token case is preserved
+        self.assertIn("ghp_AbCdEfGhIjKlMnOpQrStUvWxYz1234567890", result)
+
 
 class CaseInsensitiveConfigTests(TestCase):
     def test_case_insensitive(self) -> None:
@@ -562,6 +577,25 @@ class CaseInsensitiveConfigTests(TestCase):
         self.assertEqual(1, len(config))
         config[("CORE",)] = "value2"
         self.assertEqual(1, len(config))  # Same key, case insensitive
+
+    def test_subsection_case_preserved(self) -> None:
+        """Test that subsection names preserve their case."""
+        config = CaseInsensitiveOrderedMultiDict()
+        # Section names should be case-insensitive, but subsection names should preserve case
+        config[("url", "https://Example.COM/Path")] = "value1"
+
+        # Can retrieve with different case section name
+        self.assertEqual("value1", config[("URL", "https://Example.COM/Path")])
+        self.assertEqual("value1", config[("url", "https://Example.COM/Path")])
+
+        # But not with different case subsection name
+        with self.assertRaises(KeyError):
+            config[("url", "https://example.com/path")]
+
+        # Verify the stored key preserves subsection case
+        stored_keys = list(config.keys())
+        self.assertEqual(1, len(stored_keys))
+        self.assertEqual(("url", "https://Example.COM/Path"), stored_keys[0])
         config[("other",)] = "value3"
         self.assertEqual(2, len(config))
 
@@ -630,8 +664,12 @@ class CaseInsensitiveConfigTests(TestCase):
     def test_nested_tuple_keys(self) -> None:
         config = CaseInsensitiveOrderedMultiDict()
         config[("branch", "master")] = "value"
-        self.assertEqual("value", config[("BRANCH", "MASTER")])
-        self.assertEqual("value", config[("Branch", "Master")])
+        # Section names are case-insensitive
+        self.assertEqual("value", config[("BRANCH", "master")])
+        self.assertEqual("value", config[("Branch", "master")])
+        # But subsection names are case-sensitive
+        with self.assertRaises(KeyError):
+            config[("branch", "MASTER")]
 
 
 class ConfigFileSetTests(TestCase):