瀏覽代碼

Add support for multivar in configs. Fixes #718

Jelmer Vernooij 3 年之前
父節點
當前提交
d590a8da10
共有 3 個文件被更改,包括 63 次插入8 次删除
  1. 3 0
      NEWS
  2. 49 8
      dulwich/config.py
  3. 11 0
      dulwich/tests/test_config.py

+ 3 - 0
NEWS

@@ -1,5 +1,8 @@
 0.20.34	UNRELEASED
 
+ * Add support for multivars in configuration.
+   (Jelmer Vernooij, #718)
+
 0.20.33	2022-03-05
 
  * Fix handling of escaped characters in ignore patterns.

+ 49 - 8
dulwich/config.py

@@ -63,7 +63,12 @@ def lower_key(key):
     return key
 
 
-class CaseInsensitiveDict(OrderedDict):
+class CaseInsensitiveOrderedMultiDict(MutableMapping):
+
+    def __init__(self):
+        self._real = []
+        self._keyed = {}
+
     @classmethod
     def make(cls, dict_in=None):
 
@@ -83,15 +88,34 @@ class CaseInsensitiveDict(OrderedDict):
 
         return out
 
-    def __setitem__(self, key, value, **kwargs):
-        key = lower_key(key)
+    def __len__(self):
+        return len(self._keyed)
 
-        super(CaseInsensitiveDict, self).__setitem__(key, value, **kwargs)
+    def keys(self):
+        return self._keyed.keys()
 
-    def __getitem__(self, item):
-        key = lower_key(item)
+    def items(self):
+        return iter(self._real)
+
+    def __iter__(self):
+        return self._keyed.__iter__()
+
+    def values(self):
+        return self._keyed.values()
+
+    def __setitem__(self, key, value):
+        self._real.append((key, value))
+        self._keyed[lower_key(key)] = value
+
+    def __delitem__(self, key):
+        key = lower_key(key)
+        del self._keyed[key]
+        for i, (actual, unused_value) in reversed(enumerate(self._real)):
+            if lower_key(actual) == key:
+                del self._real[i]
 
-        return super(CaseInsensitiveDict, self).__getitem__(key)
+    def __getitem__(self, item):
+        return self._keyed[lower_key(item)]
 
     def get(self, key, default=SENTINAL):
         try:
@@ -104,6 +128,12 @@ class CaseInsensitiveDict(OrderedDict):
 
         return default
 
+    def get_all(self, key):
+        key = lower_key(key)
+        for actual, value in self._real:
+            if lower_key(actual) == key:
+                yield value
+
     def setdefault(self, key, default=SENTINAL):
         try:
             return self[key]
@@ -234,7 +264,7 @@ class ConfigDict(Config, MutableMapping):
         if encoding is None:
             encoding = sys.getdefaultencoding()
         self.encoding = encoding
-        self._values = CaseInsensitiveDict.make(values)
+        self._values = CaseInsensitiveOrderedMultiDict.make(values)
 
     def __repr__(self):
         return "%s(%r)" % (self.__class__.__name__, self._values)
@@ -283,6 +313,17 @@ class ConfigDict(Config, MutableMapping):
 
         return section, name
 
+    def get_multivar(self, section, name):
+        section, name = self._check_section_and_name(section, name)
+
+        if len(section) > 1:
+            try:
+                return self._values[section][name]
+            except KeyError:
+                pass
+
+        return self._values[(section[0],)].get_all(name)
+
     def get(self, section, name):
         section, name = self._check_section_and_name(section, name)
 

+ 11 - 0
dulwich/tests/test_config.py

@@ -108,6 +108,11 @@ class ConfigFileTests(TestCase):
         self.assertEqual(b"bar", cf.get((b"core",), b"foo"))
         self.assertEqual(b"bar", cf.get((b"core", b"foo"), b"foo"))
 
+    def test_from_file_multiple(self):
+        cf = self.from_file(b"[core]\nfoo = bar\nfoo = blah\n")
+        self.assertEqual([b"bar", b"blah"], list(cf.get_multivar((b"core",), b"foo")))
+        self.assertEqual([], list(cf.get_multivar((b"core", ), b"blah")))
+
     def test_from_file_utf8_bom(self):
         text = "[core]\nfoo = b\u00e4r\n".encode("utf-8-sig")
         cf = self.from_file(text)
@@ -156,6 +161,12 @@ class ConfigFileTests(TestCase):
         cf = self.from_file(b"[branch.foo]\nfoo = bar\n")
         self.assertEqual(b"bar", cf.get((b"branch", b"foo"), b"foo"))
 
+    def test_write_preserve_multivar(self):
+        cf = self.from_file(b"[core]\nfoo = bar\nfoo = blah\n")
+        f = BytesIO()
+        cf.write_to_file(f)
+        self.assertEqual(b"[core]\n\tfoo = bar\n\tfoo = blah\n", f.getvalue())
+
     def test_write_to_file_empty(self):
         c = ConfigFile()
         f = BytesIO()