[vcs-log-graph] include only important branch heads into GraphLayout

Layout indexes for branch heads are saved in the GraphLayoutImpl#startLayoutIndexForHead field, and binary search is used to find a head commit by layout index.

Since all branch heads are used for building graph layout, some branch heads are contained in other branches. If a less important branch is contained in a more important branch, the less important branch does not receive its own layout index and is assigned a layout index from the more important branch. This means that GraphLayoutImpl#startLayoutIndexForHead is no longer sorted, and binary search may return incorrect results on it.

This commit includes only important branch heads with own layout index into the GraphLayout.

follow-up: 1cd228ff6107d0ca03d5f3172f9940e0d55d9e4f

GitOrigin-RevId: f3d5bf4cc85b6b8dbea81895a650e4eca8985cad
This commit is contained in:
Julia Beliaeva
2024-03-28 21:36:39 +00:00
committed by intellij-monorepo-bot
parent 1153085450
commit 3655c29472
2 changed files with 63 additions and 1 deletions
@@ -55,10 +55,14 @@ object GraphLayoutBuilder {
private fun build(graph: LinearGraph, sortedHeads: IntList): GraphLayoutImpl {
val layoutIndex = IntArray(graph.nodesCount())
val importantHeads = IntArrayList()
var currentLayoutIndex = 1
for (i in sortedHeads.indices) {
val head = sortedHeads.getInt(i)
if (layoutIndex[head] != 0) continue
importantHeads.add(head)
walk(head) { currentNode: Int ->
val firstVisit = layoutIndex[currentNode] == 0
@@ -73,6 +77,6 @@ object GraphLayoutBuilder {
}
}
return GraphLayoutImpl(layoutIndex, sortedHeads)
return GraphLayoutImpl(layoutIndex, importantHeads)
}
}
@@ -0,0 +1,58 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.vcs.log.graph.impl.permanent
import com.intellij.vcs.log.graph.api.LinearGraph
import com.intellij.vcs.log.graph.graph
import it.unimi.dsi.fastutil.ints.IntComparator
import org.junit.Assert.assertEquals
import org.junit.Test
class GraphLayoutBuilderHeadOrderTest {
@Test
fun linearGraph() {
val graph = graph {
0(1)
1(2)
2(3)
3()
}
graph.assertHeadsForCommits(branchHeads = setOf(0, 2), headComparator = order(0, 2), expectedHeads = listOf(0, 0, 0, 0))
graph.assertHeadsForCommits(branchHeads = setOf(0, 2), headComparator = order(2, 0), expectedHeads = listOf(0, 0, 2, 2))
}
@Test
fun branchingGraph() {
val graph = graph {
0(2)
1(3)
2(3)
3(4)
4()
}
graph.assertHeadsForCommits(branchHeads = setOf(0, 2), headComparator = order(0, 1, 2), expectedHeads = listOf(0, 1, 0, 0, 0))
graph.assertHeadsForCommits(branchHeads = setOf(0, 2), headComparator = order(0, 2, 1), expectedHeads = listOf(0, 1, 0, 0, 0))
graph.assertHeadsForCommits(branchHeads = setOf(0, 2), headComparator = order(1, 0, 2), expectedHeads = listOf(0, 1, 0, 1, 1))
graph.assertHeadsForCommits(branchHeads = setOf(0, 2), headComparator = order(1, 2, 0), expectedHeads = listOf(0, 1, 2, 1, 1))
graph.assertHeadsForCommits(branchHeads = setOf(0, 2), headComparator = order(2, 0, 1), expectedHeads = listOf(0, 1, 2, 2, 2))
graph.assertHeadsForCommits(branchHeads = setOf(0, 2), headComparator = order(2, 1, 0), expectedHeads = listOf(0, 1, 2, 2, 2))
}
/**
* Assert that [com.intellij.vcs.log.graph.api.GraphLayout] built for this graph has correct head commits for each commit in the graph.
*/
private fun LinearGraph.assertHeadsForCommits(branchHeads: Set<Int>, headComparator: IntComparator, expectedHeads: List<Int>) {
val graphLayout = GraphLayoutBuilder.build(this, branchHeads, headComparator)
assertEquals(expectedHeads, getHeadNodeIndices(this, graphLayout))
}
/**
* For each commit in the graph, computes corresponding head node.
*/
private fun getHeadNodeIndices(graph: LinearGraph, graphLayout: GraphLayoutImpl): List<Int> {
return List(graph.nodesCount()) { graphLayout.getOneOfHeadNodeIndex(it) }
}
private fun order(vararg list: Int): IntComparator {
return IntComparator { i, j -> list.indexOf(i) - list.indexOf(j) }
}
}