docs: async def modify_tree(self, tree, modifications):
          '''The modifications are a map of the form, {path: TreeEntry}. The tree
          can be None to indicate an empty starting tree. The entries can be
          either blobs or trees, or None to indicate a deletion. The return value
          is either the hash of the resulting tree, or None if the resulting tree
          is empty. Modifications in parent directories are done before
          modifications in subdirectories below them, so for example you can
          insert a tree at a given path and also insert more new stuff beneath
          that path, without fear of overwriting the new stuff.'''
  
          # Read the original contents of the base tree.
          if tree is None:
              entries = {}
          else:
              entries = await self.ls_tree(tree, '.')
  
          # Separate the modifications into two groups, those that refer to
          # entries at the base of this tree (e.g. 'foo'), and those that refer
          # to entries in subtrees (e.g. 'foo/bar').
          modifications_at_base = dict()
          modifications_in_subtrees = collections.defaultdict(dict)
          for path_str, entry in modifications.items():
              # Canonicalize paths to get rid of duplicate/trailing slashes.
              path = pathlib.PurePosixPath(path_str)
  
              # Check for nonsense paths.
              # TODO: Maybe stop recursive calls from repeating these checks.
              if len(path.parts) == 0:
                  raise ModifyTreeError('Cannot modify an empty path.')
              elif path.parts[0] == '/':
                  raise ModifyTreeError('Cannot modify an absolute path.')
              elif '..' in path.parts:
                  raise ModifyTreeError('.. is not allowed in tree paths.')
  
              if len(path.parts) == 1:
                  modifications_at_base[str(path)] = entry
              else:
                  first_dir = path.parts[0]
                  rest = str(pathlib.PurePosixPath(*path.parts[1:]))
                  modifications_in_subtrees[first_dir][rest] = entry
  
          # Insert or delete entries in the base tree. Note that this happens
          # before any subtree operations.
          for name, entry in modifications_at_base.items():
              if entry is None:
                  entries.pop(name, None)
              else:
                  entries[name] = entry
  
          # Recurse to compute modified subtrees. Note how we handle deletions:
          # If 'a' is a file, inserting a new file at 'a/b' will implicitly
          # delete 'a', but trying to delete 'a/b' will be a no-op and will not
          # delete 'a'.
          empty_tree = (await self.get_empty_tree())
          for name, sub_modifications in modifications_in_subtrees.items():
              subtree_base = None
              if name in entries and entries[name].type == TREE_TYPE:
                  subtree_base = entries[name].hash
              new_subtree = await self.modify_tree(subtree_base,
                                                   sub_modifications)
              if new_subtree != empty_tree:
                  entries[name] = TreeEntry(TREE_MODE, TREE_TYPE, new_subtree)
              # Delete an empty tree if it was actually a tree to begin with.
              elif name in entries and entries[name].type == TREE_TYPE:
                  del entries[name]
  
          # Return the resulting tree, or None if empty.
          if entries:
              session = self.no_index_git_session()
              tree = await session.make_tree_from_entries(entries)
              return tree
          else:
              return empty_tree