mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[vcs-log] introduce support for the "--first-parent" option
IDEA-127140 GitOrigin-RevId: cb4ad33bd66bafc950d6b679c2d3895f05841a7e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7cecc1ed74
commit
bbaf4f9f4d
@@ -305,6 +305,8 @@ vcs.log.column.hash=Hash
|
||||
# graph options
|
||||
graph.options.linear=Linearize Merges
|
||||
graph.options.linear.description=In case of merge show incoming commits on top of main branch commits as if they were rebased
|
||||
graph.options.first.parent=First Parent
|
||||
graph.options.first.parent.description=Follow only the first parent commit upon seeing a merge commit
|
||||
graph.sort.standard=Topologically
|
||||
graph.sort.standard.description=In case of merge show incoming commits first, directly below merge commit
|
||||
graph.sort.off=By Commit Date
|
||||
|
||||
@@ -67,6 +67,11 @@ interface PermanentGraph<Id> {
|
||||
*/
|
||||
data object LinearBek : Options()
|
||||
|
||||
/**
|
||||
* Follow only the first parent commit upon seeing a merge commit.
|
||||
*/
|
||||
data object FirstParent: Options()
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val Default = Base(SortType.Normal)
|
||||
|
||||
+18
-13
@@ -6,22 +6,13 @@ 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.LinearGraphUtils
|
||||
import com.intellij.vcs.log.graph.utils.UnsignedBitSet
|
||||
import com.intellij.vcs.log.graph.utils.getReachableMatchingNodes
|
||||
|
||||
class FilteredController(delegateLinearGraphController: LinearGraphController,
|
||||
permanentGraphInfo: PermanentGraphInfo<*>,
|
||||
matchedIds: Set<Int>,
|
||||
visibleHeadsIds: Set<Int>? = null) :
|
||||
class FilteredController(delegateLinearGraphController: LinearGraphController, permanentGraphInfo: PermanentGraphInfo<*>,
|
||||
buildCollapsedGraph: () -> CollapsedGraph) :
|
||||
CascadeController(delegateLinearGraphController, permanentGraphInfo) {
|
||||
|
||||
val collapsedGraph: CollapsedGraph = buildGraph(permanentGraphInfo.linearGraph.getReachableMatchingNodes(visibleHeadsIds, matchedIds))
|
||||
|
||||
private fun buildGraph(visibility: UnsignedBitSet): CollapsedGraph {
|
||||
return CollapsedGraph.newInstance(delegateController.compiledGraph, visibility).also {
|
||||
DottedFilterEdgesGenerator.update(it, 0, it.delegatedGraph.nodesCount() - 1)
|
||||
}
|
||||
}
|
||||
val collapsedGraph: CollapsedGraph = buildCollapsedGraph()
|
||||
|
||||
override fun performLinearGraphAction(action: LinearGraphController.LinearGraphAction): LinearGraphController.LinearGraphAnswer {
|
||||
// filter prohibits any actions on delegate graph for now
|
||||
@@ -41,4 +32,18 @@ class FilteredController(delegateLinearGraphController: LinearGraphController,
|
||||
override fun performAction(action: LinearGraphController.LinearGraphAction) = null
|
||||
|
||||
override fun getCompiledGraph() = collapsedGraph.compiledGraph
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(delegateController: LinearGraphController,
|
||||
permanentGraphInfo: PermanentGraphInfo<*>,
|
||||
matchedIds: Set<Int>,
|
||||
visibleHeadsIds: Set<Int>? = null): FilteredController {
|
||||
val visibility = delegateController.compiledGraph.getReachableMatchingNodes(visibleHeadsIds, matchedIds)
|
||||
return FilteredController(delegateController, permanentGraphInfo) {
|
||||
CollapsedGraph.newInstance(delegateController.compiledGraph, visibility).also {
|
||||
DottedFilterEdgesGenerator.update(it, 0, it.delegatedGraph.nodesCount() - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// 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.facade
|
||||
|
||||
import com.intellij.vcs.log.graph.api.EdgeFilter
|
||||
import com.intellij.vcs.log.graph.api.LinearGraph
|
||||
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.collapsing.EdgeStorageWrapper
|
||||
import com.intellij.vcs.log.graph.impl.permanent.GraphLayoutBuilder.getHeads
|
||||
import com.intellij.vcs.log.graph.utils.Dfs
|
||||
import com.intellij.vcs.log.graph.utils.UnsignedBitSet
|
||||
import com.intellij.vcs.log.graph.utils.impl.BitSetFlags
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
|
||||
object FirstParentController {
|
||||
fun create(delegateController: LinearGraphController, permanentGraphInfo: PermanentGraphInfo<*>,
|
||||
matchedIds: Set<Int>?, visibleHeadsIds: Set<Int>? = null): FilteredController {
|
||||
val linearGraph = permanentGraphInfo.linearGraph
|
||||
val startNodes = visibleHeadsIds ?: (permanentGraphInfo.branchNodeIds + linearGraph.getHeads())
|
||||
return FilteredController(delegateController, permanentGraphInfo) {
|
||||
buildCollapsedGraph(linearGraph, startNodes, matchedIds)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun buildCollapsedGraph(linearGraph: LinearGraph, startNodes: Set<Int>, matchedNodes: Set<Int>?): CollapsedGraph {
|
||||
val (visibleNodes, hiddenEdges) = linearGraph.getVisibleNodesAndHiddenEdges(startNodes, matchedNodes)
|
||||
|
||||
val collapsedGraph = CollapsedGraph.newInstance(LinearGraphWrapper(linearGraph, hiddenEdges = hiddenEdges), visibleNodes)
|
||||
if (matchedNodes != null) {
|
||||
DottedFilterEdgesGenerator.update(collapsedGraph, 0, collapsedGraph.delegatedGraph.nodesCount() - 1)
|
||||
}
|
||||
return collapsedGraph
|
||||
}
|
||||
|
||||
private fun LinearGraph.getVisibleNodesAndHiddenEdges(startNodes: Set<Int>, matchedNodes: Set<Int>?): Pair<UnsignedBitSet, EdgeStorageWrapper> {
|
||||
val visibleNodes = UnsignedBitSet()
|
||||
val hiddenEdges = EdgeStorageWrapper.createSimpleEdgeStorage()
|
||||
|
||||
val visited = BitSetFlags(nodesCount())
|
||||
for (start in startNodes) {
|
||||
if (start < 0) continue
|
||||
|
||||
var node = start
|
||||
while (node != Dfs.NextNode.NODE_NOT_FOUND) {
|
||||
if (visited[node]) break
|
||||
visited[node] = true
|
||||
|
||||
if (matchedNodes == null || matchedNodes.contains(node)) visibleNodes[node] = true
|
||||
|
||||
val downEdges = getAdjacentEdges(node, EdgeFilter.NORMAL_DOWN)
|
||||
downEdges.drop(1).forEach { hiddenEdges.createEdge(it) }
|
||||
node = downEdges.firstOrNull()?.downNodeIndex ?: Dfs.NextNode.NODE_NOT_FOUND
|
||||
}
|
||||
}
|
||||
return Pair(visibleNodes, hiddenEdges)
|
||||
}
|
||||
}
|
||||
+23
-17
@@ -38,29 +38,35 @@ class PermanentGraphImpl<CommitId : Any> private constructor(private val permane
|
||||
private val reachableNodes = ReachableNodes(LinearGraphUtils.asLiteLinearGraph(permanentLinearGraph))
|
||||
|
||||
private fun createFilteredController(options: PermanentGraph.Options, visibleHeads: Set<CommitId>?, matchingCommits: Set<CommitId>?): LinearGraphController {
|
||||
val baseController = when (options) {
|
||||
PermanentGraph.Options.LinearBek -> LinearBekController(BekBaseController(this, bekIntMap), this)
|
||||
val visibleHeadsIds = if (visibleHeads != null) permanentCommitsInfo.convertToNodeIds(visibleHeads) else null
|
||||
val matchingCommitIds = if (matchingCommits != null) permanentCommitsInfo.convertToNodeIds(matchingCommits) else null
|
||||
|
||||
when (options) {
|
||||
is PermanentGraph.Options.Base -> {
|
||||
when (options.sortType) {
|
||||
val baseController = when (options.sortType) {
|
||||
PermanentGraph.SortType.Normal -> BaseController(this)
|
||||
PermanentGraph.SortType.Bek -> BekBaseController(this, bekIntMap)
|
||||
}
|
||||
if (matchingCommitIds != null) {
|
||||
return FilteredController.create(baseController, this, matchingCommitIds, visibleHeadsIds)
|
||||
}
|
||||
return CollapsedController(baseController, this, visibleHeadsIds)
|
||||
}
|
||||
PermanentGraph.Options.FirstParent -> {
|
||||
val baseController = BaseController(this)
|
||||
return FirstParentController.create(baseController, this, matchingCommitIds, visibleHeadsIds)
|
||||
}
|
||||
PermanentGraph.Options.LinearBek -> {
|
||||
val baseController = LinearBekController(BekBaseController(this, bekIntMap), this)
|
||||
if (matchingCommitIds != null) {
|
||||
return FilteredController.create(baseController, this, matchingCommitIds, visibleHeadsIds)
|
||||
}
|
||||
if (visibleHeadsIds != null) {
|
||||
return BranchFilterController(baseController, this, visibleHeadsIds)
|
||||
}
|
||||
return baseController
|
||||
}
|
||||
}
|
||||
|
||||
val visibleHeadsIds = if (visibleHeads != null) permanentCommitsInfo.convertToNodeIds(visibleHeads) else null
|
||||
if (matchingCommits != null) {
|
||||
return FilteredController(baseController, this, permanentCommitsInfo.convertToNodeIds(matchingCommits), visibleHeadsIds)
|
||||
}
|
||||
|
||||
if (options == PermanentGraph.Options.LinearBek) {
|
||||
if (visibleHeadsIds != null) {
|
||||
return BranchFilterController(baseController, this, visibleHeadsIds)
|
||||
}
|
||||
return baseController
|
||||
}
|
||||
|
||||
return CollapsedController(baseController, this, visibleHeadsIds)
|
||||
}
|
||||
|
||||
fun createVisibleGraph(options: PermanentGraph.Options,
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ object GraphLayoutBuilder {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun LinearGraph.getHeads(): IntList {
|
||||
internal fun LinearGraph.getHeads(): IntList {
|
||||
val heads = IntArrayList()
|
||||
for (i in 0 until nodesCount()) {
|
||||
if (LinearGraphUtils.getUpNodes(this, i).isEmpty()) {
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// 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.facade
|
||||
|
||||
import com.intellij.vcs.log.graph.TestGraphBuilder
|
||||
import com.intellij.vcs.log.graph.api.LinearGraph
|
||||
import com.intellij.vcs.log.graph.graph
|
||||
import com.intellij.vcs.log.graph.linearBek.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class FirstParentTest {
|
||||
private fun LinearGraph.assertFirstParent(startNodes: Set<Int>, matchedNodes: Set<Int>? = null, expected: TestGraphBuilder.() -> Unit) {
|
||||
val actualGraph = FirstParentController.buildCollapsedGraph(this, startNodes, matchedNodes).compiledGraph
|
||||
assertEquals(expected, actualGraph)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun simpleMerge() {
|
||||
graph {
|
||||
0(1)
|
||||
1(2, 3)
|
||||
2(4)
|
||||
3(4)
|
||||
4()
|
||||
}.assertFirstParent(setOf(0)) {
|
||||
0(1)
|
||||
1(2)
|
||||
2(4)
|
||||
4()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun twoBranches() {
|
||||
graph {
|
||||
0(2)
|
||||
1(5)
|
||||
2(3, 4)
|
||||
3(6)
|
||||
4(5)
|
||||
5(6)
|
||||
6()
|
||||
}.assertFirstParent(setOf(0, 1)) {
|
||||
0(2)
|
||||
1(5)
|
||||
2(3)
|
||||
3(6)
|
||||
5(6)
|
||||
6()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startNotAtHead() {
|
||||
graph {
|
||||
0(1)
|
||||
1(2, 3)
|
||||
2(4)
|
||||
3(4)
|
||||
4()
|
||||
}.assertFirstParent(setOf(3)) {
|
||||
3(4)
|
||||
4()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun featureBranchMerges() {
|
||||
// main branch merged into feature, then feature merged into master
|
||||
graph {
|
||||
0(2)
|
||||
1(3)
|
||||
2(5, 3)
|
||||
3(4)
|
||||
4(6, 5)
|
||||
5(7)
|
||||
6(7)
|
||||
7()
|
||||
}.assertFirstParent(setOf(0, 1)) {
|
||||
0(2)
|
||||
1(3)
|
||||
2(5)
|
||||
3(4)
|
||||
4(6)
|
||||
5(7)
|
||||
6(7)
|
||||
7()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filteredMerge() {
|
||||
graph {
|
||||
0(1)
|
||||
1(2, 3)
|
||||
2(4)
|
||||
3(4)
|
||||
4()
|
||||
}.assertFirstParent(setOf(0), setOf(1, 3, 4)) {
|
||||
1(4.dot)
|
||||
4()
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-5
@@ -37,10 +37,11 @@ public class VcsLogGraphOptionsChooserGroup extends DefaultActionGroup {
|
||||
actions.addAll(ContainerUtil.map(sortTypes, sortType -> {
|
||||
return new SelectOptionsAction(logUI, properties, new PermanentGraph.Options.Base(sortType));
|
||||
}));
|
||||
actions.add(Separator.create(VcsLogBundle.message("action.vcs.log.graph.options.separator")));
|
||||
if (BekUtil.isLinearBekEnabled()) {
|
||||
actions.add(Separator.create(VcsLogBundle.message("action.vcs.log.graph.options.separator")));
|
||||
actions.add(new SelectOptionsAction(logUI, properties, PermanentGraph.Options.LinearBek.INSTANCE));
|
||||
actions.add(new SelectNonBaseOptionsAction(logUI, properties, PermanentGraph.Options.LinearBek.INSTANCE));
|
||||
}
|
||||
actions.add(new SelectNonBaseOptionsAction(logUI, properties, PermanentGraph.Options.FirstParent.INSTANCE));
|
||||
return actions.toArray(EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ public class VcsLogGraphOptionsChooserGroup extends DefaultActionGroup {
|
||||
}
|
||||
|
||||
private static class SelectOptionsAction extends ToggleAction implements DumbAware {
|
||||
private final PermanentGraph.Options myGraphOptions;
|
||||
protected final PermanentGraph.Options myGraphOptions;
|
||||
private final VcsLogUi myUI;
|
||||
private final VcsLogUiProperties myProperties;
|
||||
|
||||
@@ -93,14 +94,31 @@ public class VcsLogGraphOptionsChooserGroup extends DefaultActionGroup {
|
||||
|
||||
@Override
|
||||
public void setSelected(@NotNull AnActionEvent e, boolean state) {
|
||||
if (state && myProperties.exists(MainVcsLogUiProperties.GRAPH_OPTIONS)) {
|
||||
myProperties.set(MainVcsLogUiProperties.GRAPH_OPTIONS, myGraphOptions);
|
||||
if (myProperties.exists(MainVcsLogUiProperties.GRAPH_OPTIONS)) {
|
||||
myProperties.set(MainVcsLogUiProperties.GRAPH_OPTIONS, getOptionsToSet(state));
|
||||
}
|
||||
}
|
||||
|
||||
protected @NotNull PermanentGraph.Options getOptionsToSet(boolean state) {
|
||||
return myGraphOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ActionUpdateThread getActionUpdateThread() {
|
||||
return ActionUpdateThread.EDT;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SelectNonBaseOptionsAction extends SelectOptionsAction {
|
||||
SelectNonBaseOptionsAction(@NotNull VcsLogUi ui,
|
||||
@NotNull VcsLogUiProperties properties,
|
||||
@NotNull PermanentGraph.Options options) {
|
||||
super(ui, properties, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull PermanentGraph.Options getOptionsToSet(boolean state) {
|
||||
return state ? myGraphOptions : PermanentGraph.Options.Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ object GraphOptionsUtil {
|
||||
@NlsActions.ActionText get() = when (this) {
|
||||
is PermanentGraph.Options.Base -> sortType.localizedName
|
||||
PermanentGraph.Options.LinearBek -> VcsLogBundle.message("graph.options.linear")
|
||||
PermanentGraph.Options.FirstParent -> VcsLogBundle.message("graph.options.first.parent")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -33,25 +34,28 @@ object GraphOptionsUtil {
|
||||
@NlsActions.ActionDescription get() = when (this) {
|
||||
is PermanentGraph.Options.Base -> sortType.localizedDescription
|
||||
PermanentGraph.Options.LinearBek -> VcsLogBundle.message("graph.options.linear.description")
|
||||
PermanentGraph.Options.FirstParent -> VcsLogBundle.message("graph.options.first.parent.description")
|
||||
}
|
||||
|
||||
private const val BASE = "Base"
|
||||
private const val LINEAR_BEK = "LinearBek"
|
||||
private const val FIRST_PARENT = "FirstParent"
|
||||
|
||||
@JvmStatic
|
||||
val optionKindNames = listOf(BASE, LINEAR_BEK)
|
||||
val optionKindNames = listOf(BASE, LINEAR_BEK, FIRST_PARENT)
|
||||
|
||||
@JvmStatic
|
||||
val PermanentGraph.Options.kindName
|
||||
get() = when (this) {
|
||||
is PermanentGraph.Options.Base -> BASE
|
||||
PermanentGraph.Options.LinearBek -> LINEAR_BEK
|
||||
PermanentGraph.Options.FirstParent -> FIRST_PARENT
|
||||
}
|
||||
|
||||
internal fun PermanentGraph.Options.toStringList(): List<String> {
|
||||
return when (this) {
|
||||
is PermanentGraph.Options.Base -> listOf(kindName, sortType.presentation)
|
||||
PermanentGraph.Options.LinearBek -> listOf(kindName)
|
||||
PermanentGraph.Options.LinearBek, PermanentGraph.Options.FirstParent -> listOf(kindName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +65,7 @@ object GraphOptionsUtil {
|
||||
val kind = first()
|
||||
return when (kind) {
|
||||
LINEAR_BEK -> PermanentGraph.Options.LinearBek
|
||||
FIRST_PARENT -> PermanentGraph.Options.FirstParent
|
||||
BASE -> {
|
||||
if (size != 2) return null
|
||||
val sortType = PermanentGraph.SortType.entries.find { it.presentation == get(1) } ?: return null
|
||||
|
||||
@@ -25,7 +25,7 @@ class FileHistoryTest {
|
||||
fun LinearGraph.assert(startCommit: Int, startPath: FilePath, fileNamesData: FileHistoryData, result: TestGraphBuilder.() -> Unit) {
|
||||
val permanentGraphInfo = TestPermanentGraphInfo(this)
|
||||
val baseController = BaseController(permanentGraphInfo)
|
||||
val filteredController = FilteredController(baseController, permanentGraphInfo, fileNamesData.getCommits())
|
||||
val filteredController = FilteredController.create(baseController, permanentGraphInfo, fileNamesData.getCommits())
|
||||
|
||||
val historyBuilder = FileHistoryBuilder(startCommit, startPath, fileNamesData, FileHistory.EMPTY)
|
||||
historyBuilder.accept(filteredController, permanentGraphInfo)
|
||||
|
||||
Reference in New Issue
Block a user