/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.vcs.log.data; import com.intellij.openapi.util.Pair; import com.intellij.util.containers.ContainerUtil; import com.intellij.vcs.log.graph.GraphCommit; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import java.util.*; /** * Attaches the block of latest commits, which was read from the VCS, to the existing log structure. * * @author Stanislav Erokhin * @author Kirill Likhodedov */ public class VcsLogJoiner> { public final static String ILLEGAL_DATA_RELOAD_ALL = "All data is illegal - request reload all"; /** * Attaches the block of latest commits, which was read from the VCS, to the existing log structure. * * @param savedLog currently available part of the log. * @param previousRefs references saved from the previous refresh. * @param firstBlock the first n commits read from the VCS. * @param newRefs all references (branches) of the repository. * @return Total saved log with new commits properly attached to it + number of new commits attached to the log. */ @NotNull public Pair, Integer> addCommits(@NotNull List savedLog, @NotNull Collection previousRefs, @NotNull List firstBlock, @NotNull Collection newRefs) { Pair> newCommitsAndSavedGreenIndex = getNewCommitsAndSavedGreenIndex(savedLog, previousRefs, firstBlock, newRefs); Pair> redCommitsAndSavedRedIndex = getRedCommitsAndSavedRedIndex(savedLog, previousRefs, firstBlock, newRefs); Set removeCommits = redCommitsAndSavedRedIndex.second; Set allNewsCommits = newCommitsAndSavedGreenIndex.second; int unsafeBlockSize = Math.max(redCommitsAndSavedRedIndex.first, newCommitsAndSavedGreenIndex.first); List unsafePartSavedLog = new ArrayList(); for (Commit commit : savedLog.subList(0, unsafeBlockSize)) { if (!removeCommits.contains(commit.getId())) { unsafePartSavedLog.add(commit); } } unsafePartSavedLog = new NewCommitIntegrator(unsafePartSavedLog, allNewsCommits).getResultList(); return Pair.create(ContainerUtil.concat(unsafePartSavedLog, savedLog.subList(unsafeBlockSize, savedLog.size())), unsafePartSavedLog.size() - unsafeBlockSize); } @NotNull private Pair> getNewCommitsAndSavedGreenIndex(@NotNull List savedLog, @NotNull Collection previousRefs, @NotNull List firstBlock, @NotNull Collection newRefs) { Set allUnresolvedLinkedHashes = new THashSet(newRefs); allUnresolvedLinkedHashes.removeAll(previousRefs); // at this moment allUnresolvedLinkedHashes contains only NEW refs for (Commit commit : firstBlock) { allUnresolvedLinkedHashes.add(commit.getId()); allUnresolvedLinkedHashes.addAll(commit.getParents()); } for (Commit commit : firstBlock) { if (commit.getParents().size() != 0) { allUnresolvedLinkedHashes.remove(commit.getId()); } } int saveGreenIndex = getFirstUnTrackedIndex(savedLog, allUnresolvedLinkedHashes); return new Pair>(saveGreenIndex, getAllNewCommits(savedLog.subList(0, saveGreenIndex), firstBlock)); } private int getFirstUnTrackedIndex(@NotNull List commits, @NotNull Set searchHashes) { int lastIndex; for (lastIndex = 0; lastIndex < commits.size(); lastIndex++) { Commit commit = commits.get(lastIndex); if (searchHashes.size() == 0) return lastIndex; searchHashes.remove(commit.getId()); } if (searchHashes.size() != 0) throw new VcsLogRefreshNotEnoughDataException(); return lastIndex; } private Set getAllNewCommits(@NotNull List unsafeGreenPartSavedLog, @NotNull List firstBlock) { Set existedCommitHashes = ContainerUtil.newHashSet(); for (Commit commit : unsafeGreenPartSavedLog) { existedCommitHashes.add(commit.getId()); } Set allNewsCommits = ContainerUtil.newHashSet(); for (Commit newCommit : firstBlock) { if (!existedCommitHashes.contains(newCommit.getId())) { allNewsCommits.add(newCommit); } } return allNewsCommits; } @NotNull private Pair> getRedCommitsAndSavedRedIndex(@NotNull List savedLog, @NotNull Collection previousRefs, @NotNull List firstBlock, @NotNull Collection newRefs) { Set startRedCommits = new THashSet(previousRefs); startRedCommits.removeAll(newRefs); Set startGreenNodes = new THashSet(newRefs); for (Commit commit : firstBlock) { startGreenNodes.add(commit.getId()); startGreenNodes.addAll(commit.getParents()); } RedGreenSorter sorter = new RedGreenSorter(startRedCommits, startGreenNodes, savedLog); int saveRegIndex = sorter.getFirstSaveIndex(); return new Pair>(saveRegIndex, sorter.getAllRedCommit()); } private static class RedGreenSorter> { private final Set currentRed; private final Set currentGreen; private final Set allRedCommit = new THashSet(); private final List savedLog; private RedGreenSorter(Set startRedNodes, Set startGreenNodes, List savedLog) { this.currentRed = startRedNodes; this.currentGreen = startGreenNodes; this.savedLog = savedLog; } private void markRealRedNode(@NotNull CommitId node) { if (!currentRed.remove(node)) throw new IllegalStateException(ILLEGAL_DATA_RELOAD_ALL); // never happened allRedCommit.add(node); } private int getFirstSaveIndex() { for (int lastIndex = 0; lastIndex < savedLog.size(); lastIndex++) { Commit commit = savedLog.get(lastIndex); boolean isGreen = currentGreen.contains(commit.getId()); if (isGreen) { currentRed.remove(commit.getId()); currentGreen.addAll(commit.getParents()); } else { markRealRedNode(commit.getId()); currentRed.addAll(commit.getParents()); } if (currentRed.isEmpty()) return lastIndex + 1; } throw new IllegalStateException(ILLEGAL_DATA_RELOAD_ALL); // see VcsLogJoinerTest#illegalStateExceptionTest } public Set getAllRedCommit() { return allRedCommit; } } static class NewCommitIntegrator> { private final List list; private final Map newCommitsMap; private final Stack commitsStack; public NewCommitIntegrator(@NotNull List list, @NotNull Collection newCommits) { this.list = list; newCommitsMap = ContainerUtil.newHashMap(); for (Commit commit : newCommits) { newCommitsMap.put(commit.getId(), commit); } commitsStack = new Stack(); } private void insertAllUseStack() { while (!newCommitsMap.isEmpty()) { commitsStack.push(newCommitsMap.values().iterator().next()); while (!commitsStack.isEmpty()) { Commit currentCommit = commitsStack.peek(); boolean allParentsWereAdded = true; for (CommitId parentHash : currentCommit.getParents()) { Commit parentCommit = newCommitsMap.get(parentHash); if (parentCommit != null) { commitsStack.push(parentCommit); allParentsWereAdded = false; break; } } if (!allParentsWereAdded) continue; int insertIndex; Set parents = new THashSet(currentCommit.getParents()); for (insertIndex = 0; insertIndex < list.size(); insertIndex++) { Commit someCommit = list.get(insertIndex); if (parents.contains(someCommit.getId())) break; if (someCommit.getTimestamp() < currentCommit.getTimestamp()) break; } list.add(insertIndex, currentCommit); newCommitsMap.remove(currentCommit.getId()); commitsStack.pop(); } } } @NotNull public List getResultList() { insertAllUseStack(); return list; } } }