[file-history] new file history algorithm that correctly traverses merge commits where there was a rename in one of the branches

Since now indexes know changed files from all of the parents in merge commits, this information could be utilized to correctly build file history and thus fix some of the issues with it.
Essentially dfs is employed, same as before. But now algorithm works in two passes.

In the first pass, file name in each commit is calculated while doing a dfs on the visible graph.
The trick here is that now for getting changes a commit and a parent index is required (and not just a commit, as before). But in the visible graph, a commit parent is not its real parent in git.
To get a real parent, path between two commits is obtained with bfs.

In the second pass, some commits that have unrelated changes or are trivial merges are thrown out. During this stage some related trivial merges can be thrown out. This is going to be fixed later.

IDEA-170468
This commit is contained in:
Julia Beliaeva
2017-06-04 23:56:37 +03:00
parent 5ae0edd42a
commit fb545fc23e
8 changed files with 286 additions and 168 deletions
@@ -65,7 +65,7 @@ public class VisibleGraphImpl<CommitId> implements VisibleGraph<CommitId> {
public RowInfo<CommitId> getRowInfo(final int visibleRow) {
final int nodeId = myGraphController.getCompiledGraph().getNodeId(visibleRow);
assert nodeId >= 0; // todo remake for all id
return new MyRowInfo(nodeId, visibleRow);
return new RowInfoImpl(nodeId, visibleRow);
}
@Override
@@ -254,15 +254,19 @@ public class VisibleGraphImpl<CommitId> implements VisibleGraph<CommitId> {
}
}
private class MyRowInfo implements RowInfo<CommitId> {
public class RowInfoImpl implements RowInfo<CommitId> {
private final int myNodeId;
private final int myVisibleRow;
public MyRowInfo(int nodeId, int visibleRow) {
public RowInfoImpl(int nodeId, int visibleRow) {
myNodeId = nodeId;
myVisibleRow = visibleRow;
}
public int getNodeId() {
return myNodeId;
}
@NotNull
@Override
public CommitId getCommit() {
@@ -0,0 +1,71 @@
/*
* Copyright 2000-2017 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.graph.utils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcs.log.graph.api.LiteLinearGraph;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
public class BfsUtil {
public static int getCorrespondingParentIndex(@NotNull LiteLinearGraph graph, int startNode, int endNode, @NotNull Flags visited) {
List<Integer> candidates = graph.getNodes(startNode, LiteLinearGraph.NodeFilter.DOWN);
if (candidates.size() == 1) return 0;
if (candidates.contains(endNode)) return candidates.indexOf(endNode);
List<Queue<Integer>> queues = new ArrayList<>(candidates.size());
for (int candidate : candidates) {
queues.add(ContainerUtil.newLinkedList(candidate));
}
int emptyCount;
visited.setAll(false);
do {
emptyCount = 0;
for (Queue<Integer> queue : queues) {
if (queue.isEmpty()) {
emptyCount++;
}
else {
boolean found = runNextBfsStep(graph, queue, visited, endNode);
if (found) {
return queues.indexOf(queue);
}
}
}
}
while (emptyCount < queues.size());
return 0;
}
private static boolean runNextBfsStep(@NotNull LiteLinearGraph graph, @NotNull Queue<Integer> queue, @NotNull Flags visited, int target) {
while (!queue.isEmpty()) {
Integer node = queue.poll();
if (!visited.get(node)) {
visited.set(node, true);
List<Integer> next = graph.getNodes(node, LiteLinearGraph.NodeFilter.DOWN);
if (next.contains(target)) return true;
queue.addAll(next);
return false;
}
}
return false;
}
}
@@ -32,7 +32,7 @@ public class DfsUtil {
}
public interface NodeVisitor {
void enterNode(int node);
void enterNode(int node, int previousNode);
void exitNode(int node);
}
@@ -44,12 +44,10 @@ public class DfsUtil {
* And when a node is entered from down-sibling, goes to the up-siblings first.
* Then goes to the other down-siblings.
* When a node is entered the first time, enterNode is called.
* When a node is passes in the same direction, exitNode is called.
* Nothing is called when a all the siblings of the node are visited.
* When a all the siblings of the node are visited, exitNode is called.
*/
public static void walk(@NotNull LiteLinearGraph graph, int start, @NotNull NodeVisitor visitor) {
BitSetFlags visited = new BitSetFlags(graph.nodesCount(), false);
BitSetFlags visitedInSameDirection = new BitSetFlags(graph.nodesCount(), false);
Stack<Pair<Integer, Boolean>> stack = new Stack<>();
stack.push(new Pair<>(start, true)); // commit + direction of travel
@@ -60,7 +58,7 @@ public class DfsUtil {
boolean down = stack.peek().second;
if (!visited.get(currentNode)) {
visited.set(currentNode, true);
visitor.enterNode(currentNode);
visitor.enterNode(currentNode, getPreviousNode(stack));
}
for (int nextNode : graph.getNodes(currentNode, down ? LiteLinearGraph.NodeFilter.DOWN : LiteLinearGraph.NodeFilter.UP)) {
@@ -70,11 +68,6 @@ public class DfsUtil {
}
}
if (!visitedInSameDirection.get(currentNode)) {
visitedInSameDirection.set(currentNode, true);
visitor.exitNode(currentNode);
}
for (int nextNode : graph.getNodes(currentNode, down ? LiteLinearGraph.NodeFilter.UP : LiteLinearGraph.NodeFilter.DOWN)) {
if (!visited.get(nextNode)) {
stack.push(new Pair<>(nextNode, !down));
@@ -82,10 +75,18 @@ public class DfsUtil {
}
}
visitor.exitNode(currentNode);
stack.pop();
}
}
private static int getPreviousNode(@NotNull Stack<Pair<Integer, Boolean>> stack) {
if (stack.size() < 2) {
return NextNode.NODE_NOT_FOUND;
}
return stack.get(stack.size() - 2).first;
}
public static void walk(int startRowIndex, @NotNull NextNode nextNodeFun) {
IntStack stack = new IntStack();
stack.push(startRowIndex);
@@ -15,28 +15,27 @@
*/
package com.intellij.vcs.log.data.index;
import com.google.common.primitives.Ints;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.UnorderedPair;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Interner;
import com.intellij.util.containers.SmartHashSet;
import com.intellij.util.indexing.StorageException;
import com.intellij.vcs.log.impl.FatalErrorHandler;
import com.intellij.vcsUtil.VcsUtil;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TIntObjectIterator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class IndexDataGetter {
private static final Logger LOG = Logger.getInstance(IndexDataGetter.class);
@NotNull private final Project myProject;
@NotNull private final Set<VirtualFile> myRoots;
@NotNull private final VcsLogPersistentIndex.IndexStorage myIndexStorage;
@@ -85,8 +84,7 @@ public class IndexDataGetter {
VirtualFile root = VcsUtil.getVcsRootFor(myProject, path);
if (myRoots.contains(root)) {
try {
myIndexStorage.paths.iterateCommits(Collections.singleton(path), (paths, commit) -> result.add(commit, paths));
result.pack();
myIndexStorage.paths.iterateCommits(path, (changes, commit) -> result.add(commit, changes.first, changes.second));
}
catch (IOException | StorageException e) {
myFatalErrorsConsumer.consume(this, e);
@@ -96,117 +94,113 @@ public class IndexDataGetter {
return result;
}
public static class FileNamesData {
@NotNull private final Interner<Set<FilePath>> myPathsInterner = new Interner<>();
@NotNull private final TIntObjectHashMap<Set<FilePath>> myCommitsToPaths;
@NotNull private final TIntObjectHashMap<Set<UnorderedPair<FilePath>>> myCommitsToRenames;
public FileNamesData() {
myCommitsToPaths = new TIntObjectHashMap<>();
myCommitsToRenames = new TIntObjectHashMap<>();
}
public class FileNamesData {
@NotNull private final TIntObjectHashMap<Map<FilePath, List<VcsLogPathsIndex.ChangeData>>> myCommitToChanges =
new TIntObjectHashMap<>();
private boolean myHasRenames = false;
public boolean hasRenames() {
return !myCommitsToRenames.isEmpty();
return myHasRenames;
}
private void addPath(int commit, @NotNull FilePath path) {
Set<FilePath> paths = myCommitsToPaths.get(commit);
if (paths == null) {
paths = new SmartHashSet<>();
myCommitsToPaths.put(commit, paths);
public void add(int commit, @NotNull FilePath path, @NotNull List<VcsLogPathsIndex.ChangeData> changes) {
Map<FilePath, List<VcsLogPathsIndex.ChangeData>> map = myCommitToChanges.get(commit);
if (map == null) {
map = ContainerUtil.newHashMap();
myCommitToChanges.put(commit, map);
}
paths.add(path);
}
private void addRename(int commit, @NotNull Couple<FilePath> path) {
Set<UnorderedPair<FilePath>> paths = myCommitsToRenames.get(commit);
if (paths == null) {
paths = ContainerUtil.newHashSet();
myCommitsToRenames.put(commit, paths);
}
paths.add(new UnorderedPair<>(path.first, path.second));
}
private void add(int commit, @NotNull Couple<FilePath> paths) {
if (paths.second == null) {
addPath(commit, paths.first);
}
else {
addRename(commit, paths);
}
}
public boolean affects(int commit, @NotNull FilePath path) {
Set<FilePath> paths = myCommitsToPaths.get(commit);
if (paths != null && paths.contains(path)) return true;
return getRenamedPath(commit, path) != null;
}
@Nullable
public FilePath getRenamedPath(int commit, @Nullable FilePath newName) {
Set<UnorderedPair<FilePath>> renames = myCommitsToRenames.get(commit);
if (renames == null) return null;
for (UnorderedPair<FilePath> rename : renames) {
if (rename.first.equals(newName)) return rename.second;
if (rename.second.equals(newName)) return rename.first;
}
return null;
}
@Nullable
public FilePath getPreviousPath(int commit, @Nullable FilePath path) {
Set<FilePath> paths = myCommitsToPaths.get(commit);
if (paths != null && paths.contains(path)) return path;
return getRenamedPath(commit, path);
}
public void remove(int commit) {
myCommitsToPaths.remove(commit);
myCommitsToRenames.remove(commit);
}
public void retain(int commit, @NotNull FilePath path, @NotNull FilePath previousPath) {
if (path.equals(previousPath)) {
myCommitsToPaths.put(commit, myPathsInterner.intern(ContainerUtil.set(path)));
myCommitsToRenames.remove(commit);
}
else {
myCommitsToPaths.remove(commit);
myCommitsToRenames.put(commit, ContainerUtil.set(new UnorderedPair<>(path, previousPath)));
}
}
@NotNull
public Set<FilePath> getAffectedPaths(int commit) {
Set<FilePath> result = new SmartHashSet<>();
Set<FilePath> paths = myCommitsToPaths.get(commit);
if (paths != null) result.addAll(paths);
Set<UnorderedPair<FilePath>> renames = myCommitsToRenames.get(commit);
if (renames != null) {
for (UnorderedPair<FilePath> rename : renames) {
result.add(rename.first);
result.add(rename.second);
if (!myHasRenames) {
for (VcsLogPathsIndex.ChangeData data : changes) {
if (data == null) continue;
if (data.kind.equals(VcsLogPathsIndex.ChangeKind.RENAMED_FROM) || data.kind.equals(VcsLogPathsIndex.ChangeKind.RENAMED_TO)) {
myHasRenames = true;
break;
}
}
}
return result;
map.put(path, changes);
}
void pack() {
TIntObjectIterator<Set<FilePath>> iterator = myCommitsToPaths.iterator();
while (iterator.hasNext()) {
iterator.advance();
iterator.setValue(myPathsInterner.intern(iterator.value()));
@Nullable
public FilePath getPathInParentRevision(int commit, int parentIndex, @NotNull FilePath childPath) {
Map<FilePath, List<VcsLogPathsIndex.ChangeData>> filesToChangesMap = myCommitToChanges.get(commit);
LOG.assertTrue(filesToChangesMap != null);
List<VcsLogPathsIndex.ChangeData> changes = filesToChangesMap.get(childPath);
if (changes == null) return childPath;
VcsLogPathsIndex.ChangeData change = changes.get(parentIndex);
if (change == null) {
LOG.assertTrue(changes.size() > 1);
return childPath;
}
if (change.kind.equals(VcsLogPathsIndex.ChangeKind.RENAMED_FROM)) return null;
if (change.kind.equals(VcsLogPathsIndex.ChangeKind.RENAMED_TO)) {
return VcsUtil.getFilePath(myIndexStorage.paths.getPath(change.otherPath));
}
return childPath;
}
@Nullable
public FilePath getPathInChildRevision(int commit, int parentIndex, @NotNull FilePath parentPath) {
Map<FilePath, List<VcsLogPathsIndex.ChangeData>> filesToChangesMap = myCommitToChanges.get(commit);
LOG.assertTrue(filesToChangesMap != null);
List<VcsLogPathsIndex.ChangeData> changes = filesToChangesMap.get(parentPath);
if (changes == null) return parentPath;
VcsLogPathsIndex.ChangeData change = changes.get(parentIndex);
if (change == null) return parentPath;
if (change.kind.equals(VcsLogPathsIndex.ChangeKind.RENAMED_TO)) return null;
if (change.kind.equals(VcsLogPathsIndex.ChangeKind.RENAMED_FROM)) {
return VcsUtil.getFilePath(myIndexStorage.paths.getPath(change.otherPath));
}
return parentPath;
}
public boolean affects(int id, @NotNull FilePath path) {
return myCommitToChanges.containsKey(id) && myCommitToChanges.get(id).containsKey(path);
}
@NotNull
public Set<Integer> getCommits() {
return ContainerUtil.union(Ints.asList(myCommitsToPaths.keys()), Ints.asList(myCommitsToRenames.keys()));
Set<Integer> result = ContainerUtil.newHashSet();
myCommitToChanges.forEach(result::add);
return result;
}
@NotNull
public Map<Integer, FilePath> buildPathsMap() {
Map<Integer, FilePath> result = ContainerUtil.newHashMap();
myCommitToChanges.forEachEntry((commit, filesToChanges) -> {
if (filesToChanges.size() == 1) {
result.put(commit, ContainerUtil.getFirstItem(filesToChanges.keySet()));
}
else {
for (Map.Entry<FilePath, List<VcsLogPathsIndex.ChangeData>> fileToChange : filesToChanges.entrySet()) {
VcsLogPathsIndex.ChangeData changeData =
ContainerUtil.find(fileToChange.getValue(), ch -> ch != null && !ch.kind.equals(VcsLogPathsIndex.ChangeKind.RENAMED_FROM));
if (changeData != null) {
result.put(commit, fileToChange.getKey());
break;
}
}
}
return true;
});
return result;
}
public boolean isTrivialMerge(int commit, @NotNull FilePath path) {
if (!myCommitToChanges.containsKey(commit)) return false;
List<VcsLogPathsIndex.ChangeData> data = myCommitToChanges.get(commit).get(path);
// strictly speaking, the criteria for merge triviality is a little bit more tricky than this:
// some merges have just reverted changes in one of the branches
// they need to be displayed
// but we skip them instead
return data != null && data.size() > 1 && data.contains(null);
}
}
}
@@ -18,6 +18,7 @@ package com.intellij.vcs.log.data.index;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.FilePath;
@@ -76,6 +77,17 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<List<VcsLogPathsInd
Page.PAGE_SIZE, null, getVersion());
}
@Nullable
public String getPath(int pathId) {
try {
return myPathsIndexer.getPathsEnumerator().valueOf(pathId);
}
catch (IOException e) {
myPathsIndexer.myFatalErrorConsumer.consume(e);
}
return null;
}
@Override
public void flush() throws StorageException {
super.flush();
@@ -147,10 +159,10 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<List<VcsLogPathsInd
return result;
}
public void iterateCommits(@NotNull Collection<FilePath> paths, @NotNull ObjIntConsumer<Couple<FilePath>> consumer)
public void iterateCommits(@NotNull FilePath path, @NotNull ObjIntConsumer<Pair<FilePath, List<ChangeData>>> consumer)
throws IOException, StorageException {
Set<Integer> startIds = getPathIds(paths);
Set<Integer> startIds = getPathIds(Collections.singleton(path));
Set<Integer> allIds = ContainerUtil.newHashSet(startIds);
Set<Integer> newIds = ContainerUtil.newHashSet();
while (!startIds.isEmpty()) {
@@ -162,17 +174,9 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<List<VcsLogPathsInd
if (!allIds.contains(renamed)) {
newIds.add(renamed);
}
try {
FilePath renamedPath = VcsUtil.getFilePath(myPathsIndexer.myPathsEnumerator.valueOf(renamed));
consumer.accept(Couple.of(currentPath, renamedPath), commitId);
}
catch (IOException e) {
LOG.error(e);
}
}
if (otherNames.isEmpty()) {
consumer.accept(Couple.of(currentPath, null), commitId);
}
consumer.accept(Pair.create(currentPath, changesList), commitId);
});
}
startIds = ContainerUtil.newHashSet(newIds);
@@ -240,7 +244,8 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<List<VcsLogPathsInd
public Map<Integer, List<ChangeData>> map(@NotNull VcsFullCommitDetails inputData) {
Map<Integer, List<ChangeData>> result = new THashMap<>();
for (int parent = 0; parent < inputData.getParents().size(); parent++) {
int size = inputData.getParents().isEmpty() ? 1 : inputData.getParents().size();
for (int parent = 0; parent < size; parent++) {
Collection<Couple<String>> moves;
Collection<String> changedPaths;
if (inputData instanceof VcsIndexableDetails) {
@@ -285,7 +290,7 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<List<VcsLogPathsInd
}
for (int pathId : result.keySet()) {
fillDataWithNulls(result, inputData.getParents().size(), pathId);
fillDataWithNulls(result, size, pathId);
}
return result;
@@ -296,7 +301,7 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<List<VcsLogPathsInd
int afterId = myPathsEnumerator.enumerate(afterPath);
List<ChangeData> data = fillDataWithNulls(result, parent, afterId);
if (beforePath == null) {
data.add(null);
data.add(new ChangeData(ChangeKind.MODIFIED, -1));
}
else {
int beforeId = myPathsEnumerator.enumerate(beforePath);
@@ -28,14 +28,19 @@ import com.intellij.vcs.log.data.DataPack;
import com.intellij.vcs.log.data.VcsLogData;
import com.intellij.vcs.log.data.index.IndexDataGetter;
import com.intellij.vcs.log.graph.PermanentGraph;
import com.intellij.vcs.log.graph.RowInfo;
import com.intellij.vcs.log.graph.VisibleGraph;
import com.intellij.vcs.log.graph.api.LinearGraph;
import com.intellij.vcs.log.graph.api.LiteLinearGraph;
import com.intellij.vcs.log.graph.api.permanent.PermanentGraphInfo;
import com.intellij.vcs.log.graph.impl.facade.PermanentGraphImpl;
import com.intellij.vcs.log.graph.impl.facade.ReachableNodes;
import com.intellij.vcs.log.graph.impl.facade.VisibleGraphImpl;
import com.intellij.vcs.log.graph.impl.permanent.PermanentCommitsInfoImpl;
import com.intellij.vcs.log.graph.utils.DfsUtil;
import com.intellij.vcs.log.graph.utils.LinearGraphUtils;
import com.intellij.vcs.log.graph.utils.BfsUtil;
import com.intellij.vcs.log.graph.utils.impl.BitSetFlags;
import com.intellij.vcs.log.visible.CommitCountStage;
import com.intellij.vcs.log.visible.VcsLogFilterer;
import com.intellij.vcs.log.visible.VisiblePack;
@@ -73,21 +78,29 @@ class FileHistoryFilterer extends VcsLogFilterer {
VisibleGraph<Integer> visibleGraph = createVisibleGraph(dataPack, sortType, matchingHeads, filterResult.matchingCommits);
IndexDataGetter.FileNamesData namesData = ((FilteredByFileResult)filterResult).fileNamesData;
Map<Integer, FilePath> pathsMap = null;
if (namesData.hasRenames() && visibleGraph.getVisibleCommitCount() > 0) {
if (visibleGraph instanceof VisibleGraphImpl) {
int row = getCurrentRow(dataPack, visibleGraph, namesData);
if (row >= 0) {
FileHistoryRefiner refiner = new FileHistoryRefiner(visibleGraph, namesData);
if (refiner.refine(((VisibleGraphImpl)visibleGraph).getLinearGraph(), row, myFilePath)) {
FileHistoryRefiner refiner = new FileHistoryRefiner((VisibleGraphImpl)visibleGraph,
((PermanentGraphInfo<Integer>)dataPack.getPermanentGraph()).getLinearGraph(),
namesData);
if (refiner.refine(row, myFilePath)) {
// creating a vg is the most expensive task, so trying to avoid that when unnecessary
visibleGraph = createVisibleGraph(dataPack, sortType, matchingHeads, refiner.getMatchingCommits());
visibleGraph = createVisibleGraph(dataPack, sortType, matchingHeads, refiner.getPathsForCommits().keySet());
pathsMap = refiner.getPathsForCommits();
}
}
}
}
return new FileHistoryVisiblePack(dataPack, visibleGraph, filterResult.canRequestMore, filters, namesData);
if (pathsMap == null) {
pathsMap = namesData.buildPathsMap();
}
return new FileHistoryVisiblePack(dataPack, visibleGraph, filterResult.canRequestMore, filters, pathsMap);
}
@NotNull
@@ -159,55 +172,85 @@ class FileHistoryFilterer extends VcsLogFilterer {
}
private static class FileHistoryRefiner implements DfsUtil.NodeVisitor {
@NotNull private final VisibleGraph<Integer> myVisibleGraph;
@NotNull private final VisibleGraphImpl<Integer> myVisibleGraph;
@NotNull private final LiteLinearGraph myPermanentGraph;
@NotNull private final LiteLinearGraph myLinearVisibleGraph;
@NotNull private final IndexDataGetter.FileNamesData myNamesData;
@NotNull private final Stack<FilePath> myPaths;
@NotNull private final Set<Integer> myMatchingCommits;
private boolean myWasChanged;
@NotNull private final Stack<FilePath> myPaths = new Stack<>();
@NotNull private final BitSetFlags myVisibilityBuffer; // a reusable buffer for bfs
@NotNull private final Map<Integer, FilePath> myPathsForCommits = ContainerUtil.newHashMap();
@NotNull private final Set<Integer> myExcluded = ContainerUtil.newHashSet();
public FileHistoryRefiner(@NotNull VisibleGraph<Integer> visibleGraph,
public FileHistoryRefiner(@NotNull VisibleGraphImpl<Integer> visibleGraph,
@NotNull LinearGraph permanentGraph,
@NotNull IndexDataGetter.FileNamesData namesData) {
myVisibleGraph = visibleGraph;
myPermanentGraph = LinearGraphUtils.asLiteLinearGraph(permanentGraph);
myLinearVisibleGraph = LinearGraphUtils.asLiteLinearGraph(myVisibleGraph.getLinearGraph());
myNamesData = namesData;
myPaths = new Stack<>();
myMatchingCommits = ContainerUtil.newHashSet();
myWasChanged = false;
myVisibilityBuffer = new BitSetFlags(myPermanentGraph.nodesCount());
}
public boolean refine(@NotNull LinearGraph graph, int row, @NotNull FilePath startPath) {
public boolean refine(int row, @NotNull FilePath startPath) {
myPaths.push(startPath);
DfsUtil.walk(LinearGraphUtils.asLiteLinearGraph(graph), row, this);
return myWasChanged;
DfsUtil.walk(myLinearVisibleGraph, row, this);
for (int commit : myPathsForCommits.keySet()) {
FilePath path = myPathsForCommits.get(commit);
if (path != null) {
if (!myNamesData.affects(commit, path)) myExcluded.add(commit);
if (myNamesData.isTrivialMerge(commit, path)) myExcluded.add(commit);
}
}
myExcluded.forEach(myPathsForCommits::remove);
return !myExcluded.isEmpty();
}
@NotNull
public Set<Integer> getMatchingCommits() {
return myMatchingCommits;
public Map<Integer, FilePath> getPathsForCommits() {
return myPathsForCommits;
}
@Override
public void enterNode(int node) {
FilePath currentPath = myPaths.peek();
Integer commit = myVisibleGraph.getRowInfo(node).getCommit();
public void enterNode(int node, int previousNode) {
FilePath previousPath = myPaths.peek();
RowInfo<Integer> currentRowInfo = myVisibleGraph.getRowInfo(node);
int currentCommit = currentRowInfo.getCommit();
int currentNodeId = ((VisibleGraphImpl.RowInfoImpl)currentRowInfo).getNodeId();
FilePath previousPath = myNamesData.getPreviousPath(commit, currentPath);
if (previousPath != null) {
myMatchingCommits.add(commit);
if (previousNode == DfsUtil.NextNode.NODE_NOT_FOUND) {
myPathsForCommits.put(currentCommit, previousPath);
myPaths.push(previousPath);
myNamesData.retain(commit, currentPath, previousPath);
}
else {
myNamesData.remove(commit);
myWasChanged = true;
FilePath currentPath;
RowInfo<Integer> previousRowInfo = myVisibleGraph.getRowInfo(previousNode);
int previousCommit = previousRowInfo.getCommit();
int previousNodeId = ((VisibleGraphImpl.RowInfoImpl)previousRowInfo).getNodeId();
// checking which node is the parent and which is the child
if (myLinearVisibleGraph.getNodes(node, LiteLinearGraph.NodeFilter.DOWN).contains(previousNode)) {
// since in reality there is no edge between the nodes, but the whole path, we need to know, which parent is affected by this path
int parentIndex = BfsUtil.getCorrespondingParentIndex(myPermanentGraph, currentNodeId, previousNodeId, myVisibilityBuffer);
currentPath = myNamesData.getPathInChildRevision(currentCommit, parentIndex, previousPath);
}
else {
int parentIndex = BfsUtil.getCorrespondingParentIndex(myPermanentGraph, previousNodeId, currentNodeId, myVisibilityBuffer);
currentPath = myNamesData.getPathInParentRevision(previousCommit, parentIndex, previousPath);
}
myPathsForCommits.put(currentCommit, currentPath);
if (currentPath != null) myPaths.push(currentPath);
}
}
@Override
public void exitNode(int node) {
Integer commit = myVisibleGraph.getRowInfo(node).getCommit();
if (myMatchingCommits.contains(commit)) {
if (myPathsForCommits.containsKey(commit) && myPathsForCommits.get(commit) != null) {
myPaths.pop();
}
}
@@ -50,10 +50,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.*;
import java.util.List;
import java.util.Set;
import static com.intellij.util.ObjectUtils.chooseNotNull;
import static com.intellij.util.ObjectUtils.notNull;
@@ -161,8 +159,8 @@ public class FileHistoryUi extends AbstractVcsLogUi {
int commitIndex = myLogData.getStorage().getCommitIndex(details.getId(), details.getRoot());
Set<FilePath> names;
if (myVisiblePack instanceof FileHistoryVisiblePack) {
IndexDataGetter.FileNamesData namesData = ((FileHistoryVisiblePack)myVisiblePack).getNamesData();
names = namesData.getAffectedPaths(commitIndex);
Map<Integer, FilePath> namesData = ((FileHistoryVisiblePack)myVisiblePack).getNamesData();
names = Collections.singleton(namesData.get(commitIndex));
}
else {
names = myIndexDataGetter.getFileNames(myPath, commitIndex);
@@ -15,27 +15,29 @@
*/
package com.intellij.vcs.log.history;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.vcs.log.VcsLogFilterCollection;
import com.intellij.vcs.log.data.DataPackBase;
import com.intellij.vcs.log.data.index.IndexDataGetter;
import com.intellij.vcs.log.graph.VisibleGraph;
import com.intellij.vcs.log.visible.VisiblePack;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
public class FileHistoryVisiblePack extends VisiblePack {
@NotNull private final IndexDataGetter.FileNamesData myNamesData;
@NotNull private final Map<Integer, FilePath> myNamesData;
public FileHistoryVisiblePack(@NotNull DataPackBase dataPack,
@NotNull VisibleGraph<Integer> graph,
boolean canRequestMore,
@NotNull VcsLogFilterCollection filters,
@NotNull IndexDataGetter.FileNamesData namesData) {
@NotNull Map<Integer, FilePath> namesData) {
super(dataPack, graph, canRequestMore, filters);
myNamesData = namesData;
}
@NotNull
public IndexDataGetter.FileNamesData getNamesData() {
public Map<Integer, FilePath> getNamesData() {
return myNamesData;
}
}