浏览代码

When merging two configuration files, error gracefully if the two files do not adhere to the same format.

Dan Helfman 2 年之前
父节点
当前提交
f60e97d5bf
共有 3 个文件被更改,包括 57 次插入0 次删除
  1. 2 0
      NEWS
  2. 7 0
      borgmatic/config/load.py
  3. 48 0
      tests/integration/config/test_load.py

+ 2 - 0
NEWS

@@ -4,6 +4,8 @@
    variables available for explicit use in your commands. See the documentation for more
    information: https://torsion.org/borgmatic/docs/how-to/run-arbitrary-borg-commands/
  * #719: Fix an error when running "borg key export" through borgmatic.
+ * When merging two configuration files, error gracefully if the two files do not adhere to the same
+   format.
 
 1.7.15
  * #326: Add configuration options and command-line flags for backing up a database from one

+ 7 - 0
borgmatic/config/load.py

@@ -225,6 +225,8 @@ def deep_merge_nodes(nodes):
     The purpose of deep merging like this is to support, for instance, merging one borgmatic
     configuration file into another for reuse, such that a configuration section ("retention",
     etc.) does not completely replace the corresponding section in a merged file.
+
+    Raise ValueError if a merge is implied using two incompatible types.
     '''
     # Map from original node key/value to the replacement merged node. DELETED_NODE as a replacement
     # node indications deletion.
@@ -239,6 +241,11 @@ def deep_merge_nodes(nodes):
 
             # If the keys match and the values are different, we need to merge these two A and B nodes.
             if a_key.tag == b_key.tag and a_key.value == b_key.value and a_value != b_value:
+                if not type(a_value) is type(b_value):
+                    raise ValueError(
+                        f'Incompatible types found when trying to merge "{a_key.value}:" values across configuration files: {type(a_value).id} and {type(b_value).id}'
+                    )
+
                 # Since we're merging into the B node, consider the A node a duplicate and remove it.
                 replaced_nodes[(a_key, a_value)] = DELETED_NODE
 

+ 48 - 0
tests/integration/config/test_load.py

@@ -702,6 +702,54 @@ def test_deep_merge_nodes_appends_colliding_sequence_values():
     assert [item.value for item in options[0][1].value] == ['echo 1', 'echo 2', 'echo 3', 'echo 4']
 
 
+def test_deep_merge_nodes_errors_on_colliding_values_of_different_types():
+    node_values = [
+        (
+            module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
+            module.ruamel.yaml.nodes.MappingNode(
+                tag='tag:yaml.org,2002:map',
+                value=[
+                    (
+                        module.ruamel.yaml.nodes.ScalarNode(
+                            tag='tag:yaml.org,2002:str', value='before_backup'
+                        ),
+                        module.ruamel.yaml.nodes.ScalarNode(
+                            tag='tag:yaml.org,2002:str', value='echo oopsie daisy'
+                        ),
+                    ),
+                ],
+            ),
+        ),
+        (
+            module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
+            module.ruamel.yaml.nodes.MappingNode(
+                tag='tag:yaml.org,2002:map',
+                value=[
+                    (
+                        module.ruamel.yaml.nodes.ScalarNode(
+                            tag='tag:yaml.org,2002:str', value='before_backup'
+                        ),
+                        module.ruamel.yaml.nodes.SequenceNode(
+                            tag='tag:yaml.org,2002:seq',
+                            value=[
+                                module.ruamel.yaml.ScalarNode(
+                                    tag='tag:yaml.org,2002:str', value='echo 3'
+                                ),
+                                module.ruamel.yaml.ScalarNode(
+                                    tag='tag:yaml.org,2002:str', value='echo 4'
+                                ),
+                            ],
+                        ),
+                    ),
+                ],
+            ),
+        ),
+    ]
+
+    with pytest.raises(ValueError):
+        module.deep_merge_nodes(node_values)
+
+
 def test_deep_merge_nodes_only_keeps_mapping_values_tagged_with_retain():
     node_values = [
         (