private static @NotNull GitFileAnnotation parseAnnotations(@NotNull Project project,
                                                             @Nullable VcsRevisionNumber revision,
                                                             @NotNull VirtualFile file,
                                                             @NotNull FilePath filePath,
                                                             @NotNull VirtualFile root,
                                                             @NotNull String output) throws VcsException {
    Interner<FilePath> pathInterner = Interner.createInterner();

    if (StringUtil.isEmpty(output)) {
      LOG.warn("Git annotations are empty for file " + file.getPath() + " in revision " + revision);
    }

    VcsUserRegistry userRegistry = project.getService(VcsUserRegistry.class);

    try {
      List<LineInfo> lines = new ArrayList<>();
      HashMap<String, CommitInfo> commits = new HashMap<>();
      for (StringScanner s = new StringScanner(output); s.hasMoreData(); ) {
        // parse header line
        String commitHash = s.spaceToken();
        if (commitHash.equals(GitRevisionNumber.NOT_COMMITTED_HASH)) {
          commitHash = null;
        }
        String s0 = s.spaceToken();
        int originalLineNum = Integer.parseInt(s0);
        String s1 = s.spaceToken();
        int lineNum = Integer.parseInt(s1);
        s.nextLine(); // skip number of lines in this group (if present)
        // parse commit information
        CommitInfo commit = commits.get(commitHash);
        if (commit != null || commitHash == null) {
          while (s.hasMoreData() && !s.startsWith('\t')) {
            s.nextLine();
          }
        }
        else {
          Date committerDate = null;
          FilePath commitFilePath = null;
          String subject = null;
          String authorName = null;
          String authorEmail = null;
          Date authorDate = null;
          String previousRevision = null;
          FilePath previousFilePath = null;

          while (s.hasMoreData() && !s.startsWith('\t')) {
            String key = s.spaceToken();
            String value = s.line();
            if (SUBJECT_KEY.equals(key)) {
              subject = value;
            }
            else if (AUTHOR_KEY.equals(key)) {
              authorName = value;
            }
            else if (AUTHOR_TIME_KEY.equals(key)) {
              authorDate = GitUtil.parseTimestamp(value);
            }
            else if (COMMITTER_TIME_KEY.equals(key)) {
              committerDate = GitUtil.parseTimestamp(value);
            }
            else if (FILENAME_KEY.equals(key)) {
              commitFilePath = VcsUtil.getFilePath(root, value);
            }
            else if (AUTHOR_EMAIL_KEY.equals(key)) {
              authorEmail = value;
              if (authorEmail.startsWith("<") && authorEmail.endsWith(">")) {
                authorEmail = authorEmail.substring(1, authorEmail.length() - 1);
              }
            }
            else if (PREVIOUS_KEY.equals(key)) {
              int index = value.indexOf(' ');
              if (index != -1) {
                previousRevision = value.substring(0, index);
                previousFilePath = VcsUtil.getFilePath(root, value.substring(index + 1));
              }
            }
          }

          if (authorDate == null ||
              committerDate == null ||
              commitFilePath == null ||
              authorName == null ||
              authorEmail == null ||
              subject == null) {
            throw new VcsException(GitBundle.message("annotate.output.lack.data", lineNum));
          }

          GitRevisionNumber revisionNumber = new GitRevisionNumber(commitHash);
          VcsUser author = userRegistry.createUser(authorName, authorEmail);
          GitRevisionNumber previousRevisionNumber = previousRevision != null ? new GitRevisionNumber(previousRevision) : null;


          commitFilePath = pathInterner.intern(commitFilePath);
          if (previousFilePath != null) previousFilePath = pathInterner.intern(previousFilePath);

          commit = new CommitInfo(project, revisionNumber, commitFilePath, committerDate, authorDate, author, subject,
                                  previousRevisionNumber, previousFilePath);
          commits.put(commitHash, commit);
        }
        s.nextLine();

        int expectedLineNum = lines.size() + 1;
        if (lineNum != expectedLineNum) {
          throw new VcsException(GitBundle.message("annotate.line.mismatch.exception", lineNum, expectedLineNum));
        }

        //noinspection ConstantConditions
        LineInfo lineInfo = new LineInfo(commit, lineNum, originalLineNum);
        lines.add(lineInfo);
      }
      return new GitFileAnnotation(project, file, filePath, revision, lines);
    }
    catch (ProcessCanceledException e) {
      throw e;
    }
    catch (Exception e) {
      LOG.error("Couldn't parse annotation: " + e.getMessage(), e, new Attachment("output.txt", output));
      throw new VcsException(e);
    }
  }
private static @NotNull GitFileAnnotation parseAnnotations(@NotNull Project project,
                                                             @Nullable VcsRevisionNumber revision,
                                                             @NotNull VirtualFile file,
                                                             @NotNull FilePath filePath,
                                                             @NotNull VirtualFile root,
                                                             @NotNull String output)