'Update destination_blocks for the sub-dag rooted at block_key to be like the one in
source_blocks excluding blacklist.
Return any newly discovered orphans (as a set)'

@contract(block_key=BlockKey, source_blocks='dict(BlockKey:    *)', destination_blocks='dict(BlockKey:    *)', blacklist='list(BlockKey)    |    str')
def _copy_subdag(self, user_id, destination_version, block_key, source_blocks, destination_blocks, blacklist):

    orphans = set()
    destination_block = destination_blocks.get(block_key)
    new_block = source_blocks[block_key]
    if destination_block:
        source_children = new_block.fields.get('children', [])
        existing_children = destination_block.fields.get('children', [])
        destination_reordered = SparseList()
        for child in existing_children:
            try:
                index = source_children.index(child)
                destination_reordered[index] = child
            except ValueError:
                orphans.add(BlockKey(*child))
        if (blacklist != EXCLUDE_ALL):
            for (index, child) in enumerate(source_children):
                if (child not in blacklist):
                    destination_reordered[index] = child
        previous_version = destination_block.edit_info.update_version
        destination_block = copy.deepcopy(new_block)
        destination_block.fields['children'] = destination_reordered.compact_list()
        destination_block.edit_info.previous_version = previous_version
        destination_block.edit_info.update_version = destination_version
        destination_block.edit_info.edited_by = user_id
        destination_block.edit_info.edited_on = datetime.datetime.now(UTC)
    else:
        destination_block = self._new_block(user_id, new_block.block_type, self._filter_blacklist(copy.copy(new_block.fields), blacklist), new_block.definition, destination_version, raw=True, asides=new_block.asides, block_defaults=new_block.defaults)
        for (key, val) in new_block.edit_info.to_storable().iteritems():
            if (getattr(destination_block.edit_info, key) is None):
                setattr(destination_block.edit_info, key, val)
    destination_block.edit_info.source_version = (new_block.edit_info.source_version or new_block.edit_info.update_version)
    if (blacklist != EXCLUDE_ALL):
        for child in destination_block.fields.get('children', []):
            if (child not in blacklist):
                orphans.update(self._copy_subdag(user_id, destination_version, BlockKey(*child), source_blocks, destination_blocks, blacklist))
    destination_blocks[block_key] = destination_block
    return orphans
