def merge_nodes(mine: TscnFile, theirs: TscnFile, result: TscnFile, resolution: Callable[[str, Printable, Printable], Resolution]): # We start at the root node and work down the tree of each file # we tree to keep node order as is as far as possible. assert len(mine.nodes) > 0, "MINE file has no node. This is invalid." assert len(theirs.nodes) > 0, "THEIRS file has no node. This is invalid." # Now we need to partition up the nodes so we can do a comparison between them and start the merging partitions: list[NodePartition] = partition_nodes(mine.nodes, theirs.nodes) # Now we can walk the partitions and merge if necessary for partition in partitions: if partition.is_only_mine: # node exists only on my side, so we can just add it to the result for my_node, _ in partition.nodes: result.nodes.append(my_node) elif partition.is_only_theirs: # node exists only on their side, so we can just add it to the result for _, their_node in partition.nodes: result.nodes.append(their_node) else: # node exists on both sides and we need to merge it for my_node, their_node in partition.nodes: if not my_node.represents_same_thing(their_node): # If the two nodes represent different things, there is no way to merge them as the properties are # dependent on the type of the node and therefore merging properties of two different # types together just makes not any sense. Therefore in this case the only thing # we can do is ask which of the both nodes we would like to have. decision = resolution( "The same node has different types/scripts. " "This cannot be merged. Which node should I take?", my_node, their_node) if decision == Resolution.MINE: result.nodes.append(my_node) elif decision == Resolution.THEIRS: result.nodes.append(their_node) else: assert False, "Unexpected input." else: # The nodes represent the same thing. Now we can compare their properties and build a result node. result_node = Node(my_node.type, my_node.name, my_node.parent, my_node.instance, my_node.instance_placeholder) properties: DiffResult[tuple[str, Value]] = diff_bag_properties( my_node, their_node) # Stuff that is same in both nodes, goes to the result for (key, value), _ in properties.same: result_node.set_property(key, value) # Stuff that is in my node only, also goes into the result for key, value in properties.only_in_mine: result_node.set_property(key, value) # Stuff that is in their node only, also goes into the result for key, value in properties.only_in_theirs: result_node.set_property(key, value) # Stuff that is different in both needs to be decided upon for (key, my_value), (_, their_value) in properties.different: decision = resolution( f"Node {result_node.full_path_reference().to_string()} -> " f"Property \"{key}\" is different in both sides. " f"Which one should I take?", my_value, their_value) if decision == Resolution.MINE: result_node.set_property(key, my_value) elif decision == Resolution.THEIRS: result_node.set_property(key, their_value) else: assert False, "Unexpected input." result.nodes.append(result_node)