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

This commit is contained in:
Dan Helfman 2023-06-26 16:46:09 -07:00
parent 44f9ab95f9
commit f60e97d5bf
3 changed files with 57 additions and 0 deletions

2
NEWS
View file

@ -4,6 +4,8 @@
variables available for explicit use in your commands. See the documentation for more 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/ information: https://torsion.org/borgmatic/docs/how-to/run-arbitrary-borg-commands/
* #719: Fix an error when running "borg key export" through borgmatic. * #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 1.7.15
* #326: Add configuration options and command-line flags for backing up a database from one * #326: Add configuration options and command-line flags for backing up a database from one

View file

@ -225,6 +225,8 @@ def deep_merge_nodes(nodes):
The purpose of deep merging like this is to support, for instance, merging one borgmatic 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", configuration file into another for reuse, such that a configuration section ("retention",
etc.) does not completely replace the corresponding section in a merged file. 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 # Map from original node key/value to the replacement merged node. DELETED_NODE as a replacement
# node indications deletion. # 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 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 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. # 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 replaced_nodes[(a_key, a_value)] = DELETED_NODE

View file

@ -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'] 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(): def test_deep_merge_nodes_only_keeps_mapping_values_tagged_with_retain():
node_values = [ node_values = [
( (