Merge remote-tracking branch 'origin/master'

This commit is contained in:
Roman Shevchenko
2018-11-09 19:03:48 +01:00
12 changed files with 294 additions and 176 deletions
@@ -20,7 +20,7 @@ import com.intellij.vcs.log.graph.api.elements.GraphElement;
import com.intellij.vcs.log.graph.api.permanent.PermanentGraphInfo;
import com.intellij.vcs.log.graph.impl.facade.CascadeController;
import com.intellij.vcs.log.graph.impl.facade.LinearGraphController;
import com.intellij.vcs.log.graph.impl.facade.ReachableNodes;
import com.intellij.vcs.log.graph.utils.GraphUtilKt;
import com.intellij.vcs.log.graph.utils.UnsignedBitSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -35,7 +35,7 @@ public class BranchFilterController extends CascadeController {
@NotNull PermanentGraphInfo<?> permanentGraphInfo,
@Nullable Set<Integer> idsOfVisibleBranches) {
super(delegateLinearGraphController, permanentGraphInfo);
myVisibility = ReachableNodes.getReachableNodes(myPermanentGraphInfo.getLinearGraph(), idsOfVisibleBranches);
myVisibility = GraphUtilKt.getReachableNodes(myPermanentGraphInfo.getLinearGraph(), idsOfVisibleBranches);
myCollapsedGraph = update();
}
@@ -24,7 +24,7 @@ import com.intellij.vcs.log.graph.api.permanent.PermanentGraphInfo;
import com.intellij.vcs.log.graph.impl.facade.CascadeController;
import com.intellij.vcs.log.graph.impl.facade.GraphChanges;
import com.intellij.vcs.log.graph.impl.facade.LinearGraphController;
import com.intellij.vcs.log.graph.impl.facade.ReachableNodes;
import com.intellij.vcs.log.graph.utils.GraphUtilKt;
import com.intellij.vcs.log.graph.utils.UnsignedBitSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -38,7 +38,7 @@ public class CollapsedController extends CascadeController {
@NotNull final PermanentGraphInfo<?> permanentGraphInfo,
@Nullable Set<Integer> idsOfVisibleBranches) {
super(delegateLinearGraphController, permanentGraphInfo);
UnsignedBitSet initVisibility = ReachableNodes.getReachableNodes(permanentGraphInfo.getLinearGraph(), idsOfVisibleBranches);
UnsignedBitSet initVisibility = GraphUtilKt.getReachableNodes(permanentGraphInfo.getLinearGraph(), idsOfVisibleBranches);
myCollapsedGraph = CollapsedGraph.newInstance(getDelegateController().getCompiledGraph(), initVisibility);
}
@@ -20,6 +20,7 @@ import com.intellij.vcs.log.graph.api.elements.GraphElement;
import com.intellij.vcs.log.graph.api.permanent.PermanentGraphInfo;
import com.intellij.vcs.log.graph.collapsing.CollapsedGraph;
import com.intellij.vcs.log.graph.collapsing.DottedFilterEdgesGenerator;
import com.intellij.vcs.log.graph.utils.DfsWalk;
import com.intellij.vcs.log.graph.utils.LinearGraphUtils;
import com.intellij.vcs.log.graph.utils.UnsignedBitSet;
import org.jetbrains.annotations.NotNull;
@@ -44,13 +45,13 @@ public class FilteredController extends CascadeController {
UnsignedBitSet initVisibility = new UnsignedBitSet();
if (visibleHeadsIds != null) {
ReachableNodes getter = new ReachableNodes(LinearGraphUtils.asLiteLinearGraph(myPermanentGraphInfo.getLinearGraph()));
getter.walkDown(visibleHeadsIds, node -> {
new DfsWalk(visibleHeadsIds, myPermanentGraphInfo.getLinearGraph()).walk(true, node -> {
if (matchedIds.contains(node)) initVisibility.set(node, true);
return true;
});
}
else {
for (Integer matchedId: matchedIds) initVisibility.set(matchedId, true);
for (Integer matchedId : matchedIds) initVisibility.set(matchedId, true);
}
myCollapsedGraph = CollapsedGraph.newInstance(delegateLinearGraphController.getCompiledGraph(), initVisibility);
@@ -17,14 +17,14 @@
package com.intellij.vcs.log.graph.impl.facade
import com.intellij.util.Consumer
import com.intellij.vcs.log.graph.api.LinearGraph
import com.intellij.vcs.log.graph.api.LiteLinearGraph
import com.intellij.vcs.log.graph.utils.*
import com.intellij.vcs.log.graph.utils.DfsWalk
import com.intellij.vcs.log.graph.utils.Flags
import com.intellij.vcs.log.graph.utils.impl.BitSetFlags
import java.util.*
class ReachableNodes(private val graph: LiteLinearGraph) {
private val flags: Flags = BitSetFlags(graph.nodesCount())
private val visited: Flags = BitSetFlags(graph.nodesCount())
fun getContainingBranches(nodeIndex: Int, branchNodeIndexes: Collection<Int>): Set<Int> {
val result = HashSet<Int>()
@@ -45,46 +45,9 @@ class ReachableNodes(private val graph: LiteLinearGraph) {
}
fun walk(startNodes: Collection<Int>, goDown: Boolean, consumer: (Int) -> Boolean) {
synchronized(flags) {
flags.setAll(false)
for (start in startNodes) {
if (start < 0) continue
if (flags.get(start)) continue
flags.set(start, true)
if (!consumer(start)) return
walk(start) nextNode@{ currentNode ->
for (downNode in graph.getNodes(currentNode, if (goDown) LiteLinearGraph.NodeFilter.DOWN else LiteLinearGraph.NodeFilter.UP)) {
if (!flags.get(downNode)) {
flags.set(downNode, true)
if (!consumer(downNode)) return@nextNode Dfs.NextNode.EXIT
return@nextNode downNode
}
}
Dfs.NextNode.NODE_NOT_FOUND
}
}
}
}
companion object {
@JvmStatic
fun getReachableNodes(graph: LinearGraph, headNodeIndexes: Set<Int>?): UnsignedBitSet {
if (headNodeIndexes == null) {
val nodesVisibility = UnsignedBitSet()
nodesVisibility.set(0, graph.nodesCount() - 1, true)
return nodesVisibility
}
val result = UnsignedBitSet()
val reachableNodes = ReachableNodes(LinearGraphUtils.asLiteLinearGraph(graph))
reachableNodes.walk(headNodeIndexes, true) { node: Int ->
result.set(node, true)
true
}
return result
synchronized(visited) {
visited.setAll(false)
DfsWalk(startNodes, graph, visited).walk(goDown, consumer)
}
}
}
@@ -1,71 +0,0 @@
/*
* 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 getCorrespondingParent(@NotNull LiteLinearGraph graph, int startNode, int endNode, @NotNull Flags visited) {
List<Integer> candidates = graph.getNodes(startNode, LiteLinearGraph.NodeFilter.DOWN);
if (candidates.size() == 1) return candidates.get(0);
if (candidates.contains(endNode)) return 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 candidates.get(queues.indexOf(queue));
}
}
}
}
while (emptyCount < queues.size());
return candidates.get(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;
}
}
@@ -0,0 +1,44 @@
/*
* 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
class BfsWalk(val start: Int, private val graph: LiteLinearGraph, private val visited: Flags) {
private val queue = ContainerUtil.newLinkedList(start)
fun isFinished() = queue.isEmpty()
fun step(): List<Int> {
while (!queue.isEmpty()) {
val node = queue.poll()
if (!visited.get(node)) {
visited.set(node, true)
val next = graph.getNodes(node, LiteLinearGraph.NodeFilter.DOWN)
queue.addAll(next)
return next
}
}
return emptyList()
}
fun walk() {
while (!isFinished()) {
step()
}
}
}
@@ -16,8 +16,8 @@
package com.intellij.vcs.log.graph.utils
import com.intellij.openapi.util.Ref
import com.intellij.util.containers.IntStack
import com.intellij.vcs.log.graph.api.LinearGraph
import com.intellij.vcs.log.graph.api.LiteLinearGraph
import com.intellij.vcs.log.graph.utils.impl.BitSetFlags
@@ -33,6 +33,54 @@ object Dfs {
}
}
private fun walk(start: Int, stack: IntStack, nextNodeFun: (Int) -> Int) {
stack.push(start)
while (!stack.empty()) {
val nextNode = nextNodeFun(stack.peek())
if (nextNode == Dfs.NextNode.EXIT) return
if (nextNode != Dfs.NextNode.NODE_NOT_FOUND) {
stack.push(nextNode)
}
else {
stack.pop()
}
}
stack.clear()
}
fun walk(start: Int, nextNodeFun: (Int) -> Int) {
walk(start, IntStack(), nextNodeFun)
}
class DfsWalk(private val startNodes: Collection<Int>, private val graph: LiteLinearGraph, private val visited: Flags) {
private val stack = IntStack()
constructor(startNodes: Collection<Int>, linearGraph: LinearGraph) :
this(startNodes, LinearGraphUtils.asLiteLinearGraph(linearGraph), BitSetFlags(linearGraph.nodesCount()))
fun walk(goDown: Boolean, consumer: (Int) -> Boolean) {
for (start in startNodes) {
if (start < 0) continue
if (visited.get(start)) continue
visited.set(start, true)
if (!consumer(start)) return
walk(start, stack) nextNode@{ currentNode ->
for (downNode in graph.getNodes(currentNode, if (goDown) LiteLinearGraph.NodeFilter.DOWN else LiteLinearGraph.NodeFilter.UP)) {
if (!visited.get(downNode)) {
visited.set(downNode, true)
if (!consumer(downNode)) return@nextNode Dfs.NextNode.EXIT
return@nextNode downNode
}
}
Dfs.NextNode.NODE_NOT_FOUND
}
}
}
}
/*
* Depth-first walk for a graph. For each node, walks both into upward and downward siblings.
* Tries to preserve direction of travel: when a node is entered from up-sibling, goes to the down-siblings first.
@@ -87,46 +135,4 @@ private fun isDown(stack: IntStack): Boolean {
val previousNode = getPreviousNode(stack)
if (previousNode == Dfs.NextNode.NODE_NOT_FOUND) return true
return previousNode < currentNode
}
fun LiteLinearGraph.isAncestor(lowerNode: Int, upperNode: Int): Boolean {
val visited = BitSetFlags(nodesCount(), false)
val result = Ref.create(false)
walk(lowerNode) { currentNode ->
visited.set(currentNode, true)
if (currentNode == upperNode) {
result.set(true)
return@walk Dfs.NextNode.EXIT
}
if (currentNode > upperNode) {
for (nextNode in getNodes(currentNode, LiteLinearGraph.NodeFilter.UP)) {
if (!visited.get(nextNode)) {
return@walk nextNode
}
}
}
Dfs.NextNode.NODE_NOT_FOUND
}
return result.get()
}
fun walk(startRowIndex: Int, nextNodeFun: (Int) -> Int) {
val stack = IntStack()
stack.push(startRowIndex)
while (!stack.empty()) {
val nextNode = nextNodeFun(stack.peek())
if (nextNode == Dfs.NextNode.EXIT) return
if (nextNode != Dfs.NextNode.NODE_NOT_FOUND) {
stack.push(nextNode)
}
else {
stack.pop()
}
}
stack.clear()
}
}
@@ -0,0 +1,68 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.vcs.log.graph.utils
import com.intellij.openapi.util.Ref
import com.intellij.vcs.log.graph.api.LinearGraph
import com.intellij.vcs.log.graph.api.LiteLinearGraph
import com.intellij.vcs.log.graph.utils.impl.BitSetFlags
fun LinearGraph.getReachableNodes(headNodes: Set<Int>?): UnsignedBitSet {
if (headNodes == null) {
val nodesVisibility = UnsignedBitSet()
nodesVisibility.set(0, nodesCount() - 1, true)
return nodesVisibility
}
val result = UnsignedBitSet()
DfsWalk(headNodes, this).walk(true) { node: Int ->
result.set(node, true)
true
}
return result
}
fun LiteLinearGraph.isAncestor(lowerNode: Int, upperNode: Int): Boolean {
val visited = BitSetFlags(nodesCount(), false)
val result = Ref.create(false)
walk(lowerNode) { currentNode ->
visited.set(currentNode, true)
if (currentNode == upperNode) {
result.set(true)
return@walk Dfs.NextNode.EXIT
}
if (currentNode > upperNode) {
for (nextNode in getNodes(currentNode, LiteLinearGraph.NodeFilter.UP)) {
if (!visited.get(nextNode)) {
return@walk nextNode
}
}
}
Dfs.NextNode.NODE_NOT_FOUND
}
return result.get()
}
fun LiteLinearGraph.getCorrespondingParent(startNode: Int, endNode: Int, visited: Flags): Int {
val candidates = getNodes(startNode, LiteLinearGraph.NodeFilter.DOWN)
if (candidates.size == 1) return candidates[0]
if (candidates.contains(endNode)) return endNode
val bfsWalks = candidates.mapTo(mutableListOf()) { BfsWalk(it, this, visited) }
visited.setAll(false)
do {
for (walk in bfsWalks) {
if (walk.step().contains(endNode)) {
return walk.start
}
}
bfsWalks.removeIf { it.isFinished() }
}
while (bfsWalks.isNotEmpty())
return candidates[0]
}
@@ -20,6 +20,7 @@ import com.intellij.vcs.log.graph.utils.Flags;
import org.jetbrains.annotations.NotNull;
import java.util.BitSet;
import java.util.Objects;
public class BitSetFlags implements Flags {
@@ -65,4 +66,23 @@ public class BitSetFlags implements Flags {
if (index < 0) throw new IndexOutOfBoundsException("index is " + index + " which is less then zero");
if (index >= mySize) throw new IndexOutOfBoundsException("index is " + index + " and set size is " + mySize);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BitSetFlags flags = (BitSetFlags)o;
return mySize == flags.mySize &&
myBitSet.equals(flags.myBitSet);
}
@Override
public int hashCode() {
return Objects.hash(mySize, myBitSet);
}
@Override
public String toString() {
return myBitSet + ", size = " + mySize;
}
}
@@ -0,0 +1,93 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.vcs.log.graph.impl
import com.intellij.vcs.log.graph.TestGraphBuilder
import com.intellij.vcs.log.graph.asString
import com.intellij.vcs.log.graph.graph
import com.intellij.vcs.log.graph.utils.BfsWalk
import com.intellij.vcs.log.graph.utils.LinearGraphUtils
import com.intellij.vcs.log.graph.utils.getCorrespondingParent
import com.intellij.vcs.log.graph.utils.impl.BitSetFlags
import org.junit.Test
import kotlin.test.assertEquals
class BfsTests {
private fun assertCorrespondingParent(startNode: Int, endNode: Int, expectedParent: Int, graphBuilder: TestGraphBuilder.() -> Unit) {
val graph = graph(graphBuilder)
val actualParent = LinearGraphUtils.asLiteLinearGraph(graph).getCorrespondingParent(startNode, endNode, BitSetFlags(graph.nodesCount()))
assertEquals(expectedParent, actualParent,
"Incorrect parent found when walking from ${startNode} to ${endNode} in ${graph.asString(true)}")
}
@Test
fun `one parent`() {
assertCorrespondingParent(0, 5, 1) {
0(1)
1(2)
2(3, 4)
3(5)
4(5)
5()
}
}
@Test
fun `target parent`() {
assertCorrespondingParent(0, 2, 2) {
0(1, 2)
1(3)
2(3)
3()
}
}
@Test
fun `merge commit`() {
assertCorrespondingParent(0, 4, 2) {
0(1, 2)
1(3)
2(3, 4)
3()
4()
}
}
@Test
fun `test visited`() {
val graph = graph {
0(3)
1(3)
2(4)
3(4)
4(5, 6)
5(7)
6(8)
7()
8()
}
val nodesCount = graph.nodesCount()
val visited = BitSetFlags(nodesCount)
BfsWalk(0, LinearGraphUtils.asLiteLinearGraph(graph), visited).walk()
assertEquals(BitSetFlags(nodesCount).setAll(0, 3, 4, 5, 6, 7, 8), visited)
visited.setAll(false)
BfsWalk(3, LinearGraphUtils.asLiteLinearGraph(graph), visited).walk()
assertEquals(BitSetFlags(nodesCount).setAll(3, 4, 5, 6, 7, 8), visited)
visited.setAll(false)
BfsWalk(5, LinearGraphUtils.asLiteLinearGraph(graph), visited).walk()
assertEquals(BitSetFlags(nodesCount).setAll(5, 7), visited)
visited.setAll(false)
BfsWalk(8, LinearGraphUtils.asLiteLinearGraph(graph), visited).walk()
assertEquals(BitSetFlags(nodesCount).setAll(8), visited)
visited.setAll(false)
}
private fun BitSetFlags.setAll(vararg values: Int): BitSetFlags {
values.forEach { this[it] = true }
return this
}
}
@@ -19,12 +19,8 @@ import com.intellij.vcs.log.graph.api.permanent.PermanentCommitsInfo
import com.intellij.vcs.log.graph.api.permanent.PermanentGraphInfo
import com.intellij.vcs.log.graph.collapsing.CollapsedGraph
import com.intellij.vcs.log.graph.impl.facade.*
import com.intellij.vcs.log.graph.utils.BfsUtil.getCorrespondingParent
import com.intellij.vcs.log.graph.utils.Dfs
import com.intellij.vcs.log.graph.utils.LinearGraphUtils
import com.intellij.vcs.log.graph.utils.*
import com.intellij.vcs.log.graph.utils.impl.BitSetFlags
import com.intellij.vcs.log.graph.utils.isAncestor
import com.intellij.vcs.log.graph.utils.walk
import gnu.trove.*
import java.util.*
import java.util.function.BiConsumer
@@ -202,7 +198,7 @@ internal class FileHistoryRefiner(private val visibleLinearGraph: LinearGraph,
namesData.getPathInParentRevision(previousCommit, permanentCommitsInfo.getCommitId(parentIndex), previousPath.filePath)
}
val path = findPathWithoutConflict(previousNodeId, pathGetter)
path ?: pathGetter(getCorrespondingParent(permanentLinearGraph, previousNodeId, currentNodeId, visibilityBuffer))
path ?: pathGetter(permanentLinearGraph.getCorrespondingParent(previousNodeId, currentNodeId, visibilityBuffer))
}
else {
val pathGetter = { parentIndex: Int ->
@@ -210,7 +206,7 @@ internal class FileHistoryRefiner(private val visibleLinearGraph: LinearGraph,
}
val path = findPathWithoutConflict(currentNodeId, pathGetter)
// 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
path ?: pathGetter(getCorrespondingParent(permanentLinearGraph, currentNodeId, previousNodeId, visibilityBuffer))
path ?: pathGetter(permanentLinearGraph.getCorrespondingParent(currentNodeId, previousNodeId, visibilityBuffer))
}
}
@@ -5,16 +5,14 @@ import com.intellij.openapi.util.Ref
import com.intellij.vcs.log.data.DataPack
import com.intellij.vcs.log.graph.api.LinearGraph
import com.intellij.vcs.log.graph.api.permanent.PermanentGraphInfo
import com.intellij.vcs.log.graph.impl.facade.ReachableNodes
import com.intellij.vcs.log.graph.impl.facade.VisibleGraphImpl
import com.intellij.vcs.log.graph.utils.LinearGraphUtils
import com.intellij.vcs.log.graph.utils.DfsWalk
import com.intellij.vcs.log.visible.VisiblePack
fun LinearGraph.findAncestorNode(startNodeId: Int, condition: (Int) -> Boolean): Int? {
val resultNodeId = Ref<Int>()
val reachableNodes = ReachableNodes(LinearGraphUtils.asLiteLinearGraph(this))
reachableNodes.walk(setOf(startNodeId), true) { currentNodeId ->
DfsWalk(setOf(startNodeId), this).walk(true) { currentNodeId: Int ->
if (condition(currentNodeId)) {
resultNodeId.set(currentNodeId)
false // stop walk, we have found it