From fb545fc23e772f37f91cabca1c41a4adb93caa23 Mon Sep 17 00:00:00 2001 From: Julia Beliaeva Date: Wed, 19 Apr 2017 19:15:26 +0300 Subject: [PATCH] [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 --- .../graph/impl/facade/VisibleGraphImpl.java | 10 +- .../intellij/vcs/log/graph/utils/BfsUtil.java | 71 ++++++ .../intellij/vcs/log/graph/utils/DfsUtil.java | 21 +- .../vcs/log/data/index/IndexDataGetter.java | 202 +++++++++--------- .../vcs/log/data/index/VcsLogPathsIndex.java | 35 +-- .../vcs/log/history/FileHistoryFilterer.java | 97 ++++++--- .../vcs/log/history/FileHistoryUi.java | 8 +- .../log/history/FileHistoryVisiblePack.java | 10 +- 8 files changed, 286 insertions(+), 168 deletions(-) create mode 100644 platform/vcs-log/graph/src/com/intellij/vcs/log/graph/utils/BfsUtil.java diff --git a/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/impl/facade/VisibleGraphImpl.java b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/impl/facade/VisibleGraphImpl.java index cb9251b1e17e..4aa801693bb2 100644 --- a/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/impl/facade/VisibleGraphImpl.java +++ b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/impl/facade/VisibleGraphImpl.java @@ -65,7 +65,7 @@ public class VisibleGraphImpl implements VisibleGraph { public RowInfo 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 implements VisibleGraph { } } - private class MyRowInfo implements RowInfo { + public class RowInfoImpl implements RowInfo { 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() { diff --git a/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/utils/BfsUtil.java b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/utils/BfsUtil.java new file mode 100644 index 000000000000..b9df5392464d --- /dev/null +++ b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/utils/BfsUtil.java @@ -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 candidates = graph.getNodes(startNode, LiteLinearGraph.NodeFilter.DOWN); + if (candidates.size() == 1) return 0; + if (candidates.contains(endNode)) return candidates.indexOf(endNode); + + List> queues = new ArrayList<>(candidates.size()); + for (int candidate : candidates) { + queues.add(ContainerUtil.newLinkedList(candidate)); + } + + int emptyCount; + visited.setAll(false); + do { + emptyCount = 0; + for (Queue 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 queue, @NotNull Flags visited, int target) { + while (!queue.isEmpty()) { + Integer node = queue.poll(); + if (!visited.get(node)) { + visited.set(node, true); + List next = graph.getNodes(node, LiteLinearGraph.NodeFilter.DOWN); + if (next.contains(target)) return true; + queue.addAll(next); + return false; + } + } + return false; + } +} diff --git a/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/utils/DfsUtil.java b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/utils/DfsUtil.java index 6927706f0728..9c6e25be6341 100644 --- a/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/utils/DfsUtil.java +++ b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/utils/DfsUtil.java @@ -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> 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> 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); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/IndexDataGetter.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/IndexDataGetter.java index 74fd5c15069b..3de5a13e6560 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/IndexDataGetter.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/IndexDataGetter.java @@ -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 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> myPathsInterner = new Interner<>(); - @NotNull private final TIntObjectHashMap> myCommitsToPaths; - @NotNull private final TIntObjectHashMap>> myCommitsToRenames; - - public FileNamesData() { - myCommitsToPaths = new TIntObjectHashMap<>(); - myCommitsToRenames = new TIntObjectHashMap<>(); - } + public class FileNamesData { + @NotNull private final TIntObjectHashMap>> myCommitToChanges = + new TIntObjectHashMap<>(); + private boolean myHasRenames = false; public boolean hasRenames() { - return !myCommitsToRenames.isEmpty(); + return myHasRenames; } - private void addPath(int commit, @NotNull FilePath path) { - Set paths = myCommitsToPaths.get(commit); - if (paths == null) { - paths = new SmartHashSet<>(); - myCommitsToPaths.put(commit, paths); + public void add(int commit, @NotNull FilePath path, @NotNull List changes) { + Map> map = myCommitToChanges.get(commit); + if (map == null) { + map = ContainerUtil.newHashMap(); + myCommitToChanges.put(commit, map); } - paths.add(path); - } - private void addRename(int commit, @NotNull Couple path) { - Set> 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 paths) { - if (paths.second == null) { - addPath(commit, paths.first); - } - else { - addRename(commit, paths); - } - } - - public boolean affects(int commit, @NotNull FilePath path) { - Set 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> renames = myCommitsToRenames.get(commit); - if (renames == null) return null; - - for (UnorderedPair 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 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 getAffectedPaths(int commit) { - Set result = new SmartHashSet<>(); - - Set paths = myCommitsToPaths.get(commit); - if (paths != null) result.addAll(paths); - - Set> renames = myCommitsToRenames.get(commit); - if (renames != null) { - for (UnorderedPair 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> 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> filesToChangesMap = myCommitToChanges.get(commit); + LOG.assertTrue(filesToChangesMap != null); + List 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> filesToChangesMap = myCommitToChanges.get(commit); + LOG.assertTrue(filesToChangesMap != null); + List 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 getCommits() { - return ContainerUtil.union(Ints.asList(myCommitsToPaths.keys()), Ints.asList(myCommitsToRenames.keys())); + Set result = ContainerUtil.newHashSet(); + myCommitToChanges.forEach(result::add); + return result; + } + + @NotNull + public Map buildPathsMap() { + Map result = ContainerUtil.newHashMap(); + + myCommitToChanges.forEachEntry((commit, filesToChanges) -> { + if (filesToChanges.size() == 1) { + result.put(commit, ContainerUtil.getFirstItem(filesToChanges.keySet())); + } + else { + for (Map.Entry> 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 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); } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java index 38979f359bb3..aebfba3c30b5 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java @@ -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 paths, @NotNull ObjIntConsumer> consumer) + public void iterateCommits(@NotNull FilePath path, @NotNull ObjIntConsumer>> consumer) throws IOException, StorageException { - Set startIds = getPathIds(paths); + Set startIds = getPathIds(Collections.singleton(path)); Set allIds = ContainerUtil.newHashSet(startIds); Set newIds = ContainerUtil.newHashSet(); while (!startIds.isEmpty()) { @@ -162,17 +174,9 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex> map(@NotNull VcsFullCommitDetails inputData) { Map> 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> moves; Collection changedPaths; if (inputData instanceof VcsIndexableDetails) { @@ -285,7 +290,7 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex data = fillDataWithNulls(result, parent, afterId); if (beforePath == null) { - data.add(null); + data.add(new ChangeData(ChangeKind.MODIFIED, -1)); } else { int beforeId = myPathsEnumerator.enumerate(beforePath); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryFilterer.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryFilterer.java index 2830994ae338..3528ed2cd585 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryFilterer.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryFilterer.java @@ -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 visibleGraph = createVisibleGraph(dataPack, sortType, matchingHeads, filterResult.matchingCommits); IndexDataGetter.FileNamesData namesData = ((FilteredByFileResult)filterResult).fileNamesData; + Map 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)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 myVisibleGraph; + @NotNull private final VisibleGraphImpl myVisibleGraph; + @NotNull private final LiteLinearGraph myPermanentGraph; + @NotNull private final LiteLinearGraph myLinearVisibleGraph; @NotNull private final IndexDataGetter.FileNamesData myNamesData; - @NotNull private final Stack myPaths; - @NotNull private final Set myMatchingCommits; - private boolean myWasChanged; + @NotNull private final Stack myPaths = new Stack<>(); + @NotNull private final BitSetFlags myVisibilityBuffer; // a reusable buffer for bfs + @NotNull private final Map myPathsForCommits = ContainerUtil.newHashMap(); + @NotNull private final Set myExcluded = ContainerUtil.newHashSet(); - public FileHistoryRefiner(@NotNull VisibleGraph visibleGraph, + public FileHistoryRefiner(@NotNull VisibleGraphImpl 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 getMatchingCommits() { - return myMatchingCommits; + public Map 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 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 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(); } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUi.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUi.java index 9c4c503ef56b..dade82b4dffa 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUi.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUi.java @@ -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 names; if (myVisiblePack instanceof FileHistoryVisiblePack) { - IndexDataGetter.FileNamesData namesData = ((FileHistoryVisiblePack)myVisiblePack).getNamesData(); - names = namesData.getAffectedPaths(commitIndex); + Map namesData = ((FileHistoryVisiblePack)myVisiblePack).getNamesData(); + names = Collections.singleton(namesData.get(commitIndex)); } else { names = myIndexDataGetter.getFileNames(myPath, commitIndex); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryVisiblePack.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryVisiblePack.java index b541cd1401d5..91a8c1315ba1 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryVisiblePack.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryVisiblePack.java @@ -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 myNamesData; public FileHistoryVisiblePack(@NotNull DataPackBase dataPack, @NotNull VisibleGraph graph, boolean canRequestMore, @NotNull VcsLogFilterCollection filters, - @NotNull IndexDataGetter.FileNamesData namesData) { + @NotNull Map namesData) { super(dataPack, graph, canRequestMore, filters); myNamesData = namesData; } @NotNull - public IndexDataGetter.FileNamesData getNamesData() { + public Map getNamesData() { return myNamesData; } }