@NotNull
  public static DiffLog mergeTrees(
      @NotNull final PsiFileImpl fileImpl,
      @NotNull final ASTNode oldRoot,
      @NotNull final ASTNode newRoot,
      @NotNull ProgressIndicator indicator) {
    if (newRoot instanceof FileElement) {
      ((FileElement) newRoot).setCharTable(fileImpl.getTreeElement().getCharTable());
    }

    try {
      newRoot.putUserData(TREE_TO_BE_REPARSED, oldRoot);
      if (isReplaceWholeNode(fileImpl, newRoot)) {
        DiffLog treeChangeEvent =
            replaceElementWithEvents((CompositeElement) oldRoot, (CompositeElement) newRoot);
        fileImpl.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, Boolean.TRUE);

        return treeChangeEvent;
      }
      newRoot
          .getFirstChildNode(); // maybe reparsed in PsiBuilderImpl and have thrown exception here
    } catch (ReparsedSuccessfullyException e) {
      // reparsed in PsiBuilderImpl
      return e.getDiffLog();
    } finally {
      newRoot.putUserData(TREE_TO_BE_REPARSED, null);
    }

    final ASTShallowComparator comparator = new ASTShallowComparator(indicator);
    final ASTStructure treeStructure = createInterruptibleASTStructure(newRoot, indicator);

    DiffLog diffLog = new DiffLog();
    diffTrees(oldRoot, diffLog, comparator, treeStructure, indicator);
    return diffLog;
  }
  private static boolean isReplaceWholeNode(@NotNull PsiFileImpl fileImpl, @NotNull ASTNode newRoot)
      throws ReparsedSuccessfullyException {
    final Boolean data = fileImpl.getUserData(DO_NOT_REPARSE_INCREMENTALLY);
    if (data != null) fileImpl.putUserData(DO_NOT_REPARSE_INCREMENTALLY, null);

    boolean explicitlyMarkedDeep = Boolean.TRUE.equals(data);

    if (explicitlyMarkedDeep || isTooDeep(fileImpl)) {
      return true;
    }

    final ASTNode childNode =
        newRoot
            .getFirstChildNode(); // maybe reparsed in PsiBuilderImpl and have thrown exception here
    boolean childTooDeep = isTooDeep(childNode);
    if (childTooDeep) {
      childNode.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, null);
      fileImpl.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, Boolean.TRUE);
    }
    return childTooDeep;
  }