blob: fc51742b53189c10512b4c9e4eddb3e79509263b [file] [log] [blame]
/*
* 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<CommitId, Commit extends GraphCommit<CommitId>> {
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<List<Commit>, Integer> addCommits(@NotNull List<? extends Commit> savedLog,
@NotNull Collection<CommitId> previousRefs,
@NotNull List<? extends Commit> firstBlock,
@NotNull Collection<CommitId> newRefs) {
Pair<Integer, Set<Commit>> newCommitsAndSavedGreenIndex =
getNewCommitsAndSavedGreenIndex(savedLog, previousRefs, firstBlock, newRefs);
Pair<Integer, Set<CommitId>> redCommitsAndSavedRedIndex =
getRedCommitsAndSavedRedIndex(savedLog, previousRefs, firstBlock, newRefs);
Set<CommitId> removeCommits = redCommitsAndSavedRedIndex.second;
Set<Commit> allNewsCommits = newCommitsAndSavedGreenIndex.second;
int unsafeBlockSize = Math.max(redCommitsAndSavedRedIndex.first, newCommitsAndSavedGreenIndex.first);
List<Commit> unsafePartSavedLog = new ArrayList<Commit>();
for (Commit commit : savedLog.subList(0, unsafeBlockSize)) {
if (!removeCommits.contains(commit.getId())) {
unsafePartSavedLog.add(commit);
}
}
unsafePartSavedLog = new NewCommitIntegrator<CommitId, Commit>(unsafePartSavedLog, allNewsCommits).getResultList();
return Pair.create(ContainerUtil.concat(unsafePartSavedLog, savedLog.subList(unsafeBlockSize, savedLog.size())),
unsafePartSavedLog.size() - unsafeBlockSize);
}
@NotNull
private Pair<Integer, Set<Commit>> getNewCommitsAndSavedGreenIndex(@NotNull List<? extends Commit> savedLog,
@NotNull Collection<CommitId> previousRefs,
@NotNull List<? extends Commit> firstBlock,
@NotNull Collection<CommitId> newRefs) {
Set<CommitId> allUnresolvedLinkedHashes = new THashSet<CommitId>(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<Integer, Set<Commit>>(saveGreenIndex, getAllNewCommits(savedLog.subList(0, saveGreenIndex), firstBlock));
}
private int getFirstUnTrackedIndex(@NotNull List<? extends Commit> commits, @NotNull Set<CommitId> 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<Commit> getAllNewCommits(@NotNull List<? extends Commit> unsafeGreenPartSavedLog,
@NotNull List<? extends Commit> firstBlock) {
Set<CommitId> existedCommitHashes = ContainerUtil.newHashSet();
for (Commit commit : unsafeGreenPartSavedLog) {
existedCommitHashes.add(commit.getId());
}
Set<Commit> allNewsCommits = ContainerUtil.newHashSet();
for (Commit newCommit : firstBlock) {
if (!existedCommitHashes.contains(newCommit.getId())) {
allNewsCommits.add(newCommit);
}
}
return allNewsCommits;
}
@NotNull
private Pair<Integer, Set<CommitId>> getRedCommitsAndSavedRedIndex(@NotNull List<? extends Commit> savedLog,
@NotNull Collection<CommitId> previousRefs,
@NotNull List<? extends Commit> firstBlock,
@NotNull Collection<CommitId> newRefs) {
Set<CommitId> startRedCommits = new THashSet<CommitId>(previousRefs);
startRedCommits.removeAll(newRefs);
Set<CommitId> startGreenNodes = new THashSet<CommitId>(newRefs);
for (Commit commit : firstBlock) {
startGreenNodes.add(commit.getId());
startGreenNodes.addAll(commit.getParents());
}
RedGreenSorter<CommitId, Commit> sorter = new RedGreenSorter<CommitId, Commit>(startRedCommits, startGreenNodes, savedLog);
int saveRegIndex = sorter.getFirstSaveIndex();
return new Pair<Integer, Set<CommitId>>(saveRegIndex, sorter.getAllRedCommit());
}
private static class RedGreenSorter<CommitId, Commit extends GraphCommit<CommitId>> {
private final Set<CommitId> currentRed;
private final Set<CommitId> currentGreen;
private final Set<CommitId> allRedCommit = new THashSet<CommitId>();
private final List<? extends Commit> savedLog;
private RedGreenSorter(Set<CommitId> startRedNodes, Set<CommitId> startGreenNodes, List<? extends Commit> 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<CommitId> getAllRedCommit() {
return allRedCommit;
}
}
static class NewCommitIntegrator<CommitId, Commit extends GraphCommit<CommitId>> {
private final List<Commit> list;
private final Map<CommitId, Commit> newCommitsMap;
private final Stack<Commit> commitsStack;
public NewCommitIntegrator(@NotNull List<Commit> list, @NotNull Collection<Commit> newCommits) {
this.list = list;
newCommitsMap = ContainerUtil.newHashMap();
for (Commit commit : newCommits) {
newCommitsMap.put(commit.getId(), commit);
}
commitsStack = new Stack<Commit>();
}
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<CommitId> parents = new THashSet<CommitId>(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<Commit> getResultList() {
insertAllUseStack();
return list;
}
}
}