private static Groups prepareGroups(
      @NotNull VcsLogDataPack dataPack,
      @Nullable Collection<VirtualFile> visibleRoots,
      @Nullable List<List<String>> recentItems) {
    Groups filteredGroups = new Groups();
    Collection<VcsRef> allRefs = dataPack.getRefs().getBranches();
    for (Map.Entry<VirtualFile, Set<VcsRef>> entry :
        VcsLogUtil.groupRefsByRoot(allRefs).entrySet()) {
      VirtualFile root = entry.getKey();
      if (visibleRoots != null && !visibleRoots.contains(root)) continue;
      Collection<VcsRef> refs = entry.getValue();
      VcsLogProvider provider = dataPack.getLogProviders().get(root);
      VcsLogRefManager refManager = provider.getReferenceManager();
      List<RefGroup> refGroups = refManager.group(refs);

      putActionsForReferences(refGroups, filteredGroups);
    }

    if (recentItems != null) {
      for (List<String> recentItem : recentItems) {
        if (recentItem.size() == 1) {
          final String item = ContainerUtil.getFirstItem(recentItem);
          if (filteredGroups.singletonGroups.contains(item)
              || ContainerUtil.find(
                      filteredGroups.expandedGroups.values(), strings -> strings.contains(item))
                  != null) {
            continue;
          }
        }
        filteredGroups.recentGroups.add(recentItem);
      }
    }

    return filteredGroups;
  }
  @NotNull
  @Override
  public List<? extends VcsCommitMetadata> readFirstBlock(
      @NotNull VirtualFile root, @NotNull Requirements requirements) throws VcsException {
    if (!isRepositoryReady(root)) {
      return Collections.emptyList();
    }

    int commitCount = requirements.getCommitCount();
    if (requirements.isOrdered()) {
      commitCount *=
          2; // need to query more to sort them manually; this doesn't affect performance: it is
      // equal for -1000 and -2000
    }

    String[] params =
        new String[] {"HEAD", "--branches", "--remotes", "--max-count=" + commitCount};
    // NB: not specifying --tags, because it introduces great slowdown if there are many tags,
    // but makes sense only if there are heads without branch or HEAD labels (rare case). Such cases
    // are partially handles below.
    List<VcsCommitMetadata> firstBlock = GitHistoryUtils.loadMetadata(myProject, root, params);

    if (requirements instanceof VcsLogProviderRequirementsEx) {
      VcsLogProviderRequirementsEx rex = (VcsLogProviderRequirementsEx) requirements;
      // on refresh: get new tags, which point to commits not from the first block; then get
      // history, walking down just from these tags
      // on init: just ignore such tagged-only branches. The price for speed-up.
      if (!rex.isOrdered()) {
        Collection<VcsRef> newTags = getNewTags(rex.getCurrentRefs(), rex.getPreviousRefs());
        if (!newTags.isEmpty()) {
          final Set<Hash> firstBlockHashes =
              ContainerUtil.map2Set(
                  firstBlock,
                  new Function<VcsCommitMetadata, Hash>() {
                    @Override
                    public Hash fun(VcsCommitMetadata metadata) {
                      return metadata.getHash();
                    }
                  });
          List<VcsRef> unmatchedHeads = getUnmatchedHeads(firstBlockHashes, newTags);
          if (!unmatchedHeads.isEmpty()) {
            List<VcsCommitMetadata> detailsFromTaggedBranches =
                loadSomeCommitsOnTaggedBranches(root, commitCount, unmatchedHeads);
            Collection<VcsCommitMetadata> unmatchedCommits =
                getUnmatchedCommits(firstBlockHashes, detailsFromTaggedBranches);
            firstBlock.addAll(unmatchedCommits);
          }
        }
      }
    }

    if (requirements.isOrdered()) {
      firstBlock = VcsLogSorter.sortByDateTopoOrder(firstBlock);
      firstBlock =
          new ArrayList<VcsCommitMetadata>(
              firstBlock.subList(0, Math.min(firstBlock.size(), requirements.getCommitCount())));
    }
    return firstBlock;
  }
 private static String printCommits(List<VcsCommitMetadata> commits) {
   StringBuilder sb = new StringBuilder();
   for (int i = 0; i < Math.min(commits.size(), 100); i++) {
     GraphCommit<Hash> commit = commits.get(i);
     sb.append(
         String.format(
             "%s -> %s\n",
             commit.getId().toShortString(),
             StringUtil.join(
                 commit.getParents(),
                 new Function<Hash, String>() {
                   @Override
                   public String fun(Hash hash) {
                     return hash.toShortString();
                   }
                 },
                 ", ")));
   }
   return sb.toString();
 }
  @NotNull
  @Override
  public DetailedLogData readFirstBlock(
      @NotNull VirtualFile root, @NotNull Requirements requirements) throws VcsException {
    if (!isRepositoryReady(root)) {
      return LogDataImpl.empty();
    }
    GitRepository repository =
        ObjectUtils.assertNotNull(myRepositoryManager.getRepositoryForRoot(root));

    // need to query more to sort them manually; this doesn't affect performance: it is equal for
    // -1000 and -2000
    int commitCount = requirements.getCommitCount() * 2;

    String[] params =
        new String[] {"HEAD", "--branches", "--remotes", "--max-count=" + commitCount};
    // NB: not specifying --tags, because it introduces great slowdown if there are many tags,
    // but makes sense only if there are heads without branch or HEAD labels (rare case). Such cases
    // are partially handled below.

    boolean refresh =
        requirements instanceof VcsLogProviderRequirementsEx
            && ((VcsLogProviderRequirementsEx) requirements).isRefresh();

    DetailedLogData data = GitHistoryUtils.loadMetadata(myProject, root, true, params);

    Set<VcsRef> safeRefs = data.getRefs();
    Set<VcsRef> allRefs = new OpenTHashSet<VcsRef>(safeRefs, DONT_CONSIDER_SHA);
    Set<VcsRef> branches = readBranches(repository);
    addNewElements(allRefs, branches);

    Collection<VcsCommitMetadata> allDetails;
    Set<String> currentTagNames = null;
    DetailedLogData commitsFromTags = null;
    if (!refresh) {
      allDetails = data.getCommits();
    } else {
      // on refresh: get new tags, which point to commits not from the first block; then get
      // history, walking down just from these tags
      // on init: just ignore such tagged-only branches. The price for speed-up.
      VcsLogProviderRequirementsEx rex = (VcsLogProviderRequirementsEx) requirements;

      currentTagNames = readCurrentTagNames(root);
      addOldStillExistingTags(allRefs, currentTagNames, rex.getPreviousRefs());

      allDetails = newHashSet(data.getCommits());

      Set<String> previousTags =
          newHashSet(ContainerUtil.mapNotNull(rex.getPreviousRefs(), GET_TAG_NAME));
      Set<String> safeTags = newHashSet(ContainerUtil.mapNotNull(safeRefs, GET_TAG_NAME));
      Set<String> newUnmatchedTags = remove(currentTagNames, previousTags, safeTags);

      if (!newUnmatchedTags.isEmpty()) {
        commitsFromTags = loadSomeCommitsOnTaggedBranches(root, commitCount, newUnmatchedTags);
        addNewElements(allDetails, commitsFromTags.getCommits());
        addNewElements(allRefs, commitsFromTags.getRefs());
      }
    }

    StopWatch sw = StopWatch.start("sorting commits in " + root.getName());
    List<VcsCommitMetadata> sortedCommits = VcsLogSorter.sortByDateTopoOrder(allDetails);
    sortedCommits =
        sortedCommits.subList(0, Math.min(sortedCommits.size(), requirements.getCommitCount()));
    sw.report();

    if (LOG.isDebugEnabled()) {
      validateDataAndReportError(
          root, allRefs, sortedCommits, data, branches, currentTagNames, commitsFromTags);
    }

    return new LogDataImpl(allRefs, sortedCommits);
  }