Merge remote-tracking branch 'origin/master' into loki/newlog
authorKirill Likhodedov <Kirill.Likhodedov@gmail.com>
Thu, 22 Aug 2013 13:35:07 +0000 (17:35 +0400)
committerKirill Likhodedov <Kirill.Likhodedov@gmail.com>
Thu, 22 Aug 2013 13:35:07 +0000 (17:35 +0400)
Conflicts:
.idea/modules.xml
platform/platform-resources/src/META-INF/VcsExtensionPoints.xml

172 files changed:
.idea/modules.xml
platform/dvcs/dvcs.iml
platform/dvcs/src/META-INF/plugin.xml [new file with mode: 0644]
platform/dvcs/src/com/intellij/dvcs/log/VcsLogManager.java [new file with mode: 0644]
platform/dvcs/src/com/intellij/dvcs/repo/AbstractRepositoryManager.java
platform/dvcs/src/com/intellij/dvcs/repo/RepositoryManager.java
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/CommitParents.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/Hash.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/RebaseCommand.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/Ref.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/SimpleCommitParents.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsCommit.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsCommitDetails.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsCommitImpl.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsLogProvider.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsLogRefresher.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsLogSettings.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/test/com/intellij/vcs/log/HashBuildTests.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/test/com/intellij/vcs/log/HashEqualsTests.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-api/vcs-log-api.iml [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/src/META-INF/MANIFEST.MF [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/MyTimer.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/CompressedList.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/NoCompressedList.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/RuntimeGenerateCompressedList.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/UpdateRequest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/VcsLogLogger.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/generator/AbstractGenerator.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/generator/Generator.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/test/org/hanuna/gitalk/common/compressedlist/IntegerCompressedListTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/test/org/hanuna/gitalk/common/compressedlist/RunCompressedListTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-common/vcs-log-common.iml [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/CommitDetailsGetter.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/DataGetter.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/DataPack.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/FakeCommitsInfo.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/LoadingDetails.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/MiniDetailsGetter.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/RefsModel.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/VcsCommitCache.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/VcsLogDataHolder.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/VcsLogJoiner.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/rebase/FakeCommitParents.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/rebase/InteractiveRebaseBuilder.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/rebase/VcsLogActionHandler.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-data/vcs-log-data.iml [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/Graph.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/Branch.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/Edge.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/GraphElement.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/Node.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/NodeRow.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/GraphAppendBuilder.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/GraphBuilder.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/GraphDecorator.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/MutableGraph.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/FakeNode.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/MutableNode.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/MutableNodeRow.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/UsualEdge.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/FragmentManager.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/GraphFragment.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/GraphModel.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/FragmentGenerator.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/FragmentManagerImpl.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/GraphFragmentController.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/ShortFragmentGenerator.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/elements/HideFragmentEdge.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/elements/SimpleGraphFragment.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/BadVisibleEdgeNodeFixer.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/BranchVisibleNodes.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/GraphBranchShowFixer.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/GraphDecoratorImpl.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/GraphModelImpl.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/GraphStrUtils.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/GraphTestUtils.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/mutable/GraphAppendBuildTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/mutable/GraphBuilderTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/FragmentManagerTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/GraphModelTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/GraphModelUtils.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/ShortFragmentTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/CommitDataParserTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/CommitParser.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/CommitParserTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/SimpleCommitListParser.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/TimestampCommitParents.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/TimestampCommitParentsParserTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-graph/vcs-log-graph.iml [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/CommitSelectController.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/GraphPrintCell.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/GraphPrintCellModel.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/SelectController.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/ShortEdge.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/SpecialPrintElement.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/impl/GraphElementsVisibilityController.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/impl/GraphPrintCellModelImpl.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/impl/PrePrintCellModel.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/LayoutModel.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/LayoutRow.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/LayoutRowGenerator.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/MutableLayoutRow.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/test/org/hanuna/gitalk/printmodel/LayoutTestUtils.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/test/org/hanuna/gitalk/printmodel/cells/builder/LayoutModelBuilderTest.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-print-model/vcs-log-print-model.iml [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/apply-16.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/arrow-32.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/bricks-multicolor-32.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/bricks-user-32.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/cancel-16.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/cherry-pick-32.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/edit-32.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/fixup-32.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/forbidden-32.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/move-32.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/spider-1-16.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/spider-16-2.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/spider-16.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/icons/web-16.png [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/DragDropListener.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/GitLogIcons.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/RefAction.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/VcsLogUI.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/frame/ActiveSurface.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/frame/BranchesPanel.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/frame/DetailsPanel.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/frame/MainFrame.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/frame/VcsLogGraphTable.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/render/GraphCommitCellRender.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/render/PositionUtil.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/render/PrintParameters.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/render/painters/ColorGenerator.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/render/painters/GraphCellPainter.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/render/painters/RefPainter.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/render/painters/SimpleGraphCellPainter.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/tables/CommitCell.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/tables/GraphCommitCell.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/tables/GraphTableModel.java [new file with mode: 0644]
platform/dvcs/vcs-log/vcs-log-ui/vcs-log-ui.iml [new file with mode: 0644]
platform/platform-api/src/com/intellij/util/concurrency/QueueProcessor.java
platform/platform-resources-en/src/misc/registry.properties
platform/platform-resources/src/META-INF/VcsExtensionPoints.xml
platform/platform-resources/src/componentSets/VCS.xml
plugins/git4idea/git4idea.iml
plugins/git4idea/resources/icons/gitlog.png [new file with mode: 0644]
plugins/git4idea/src/META-INF/plugin.xml
plugins/git4idea/src/git4idea/GitBranch.java
plugins/git4idea/src/git4idea/GitCommit.java
plugins/git4idea/src/git4idea/GitLocalBranch.java
plugins/git4idea/src/git4idea/GitRemoteBranch.java
plugins/git4idea/src/git4idea/GitStandardRemoteBranch.java
plugins/git4idea/src/git4idea/GitSvnRemoteBranch.java
plugins/git4idea/src/git4idea/GitVcs.java
plugins/git4idea/src/git4idea/Hash.java [deleted file]
plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java
plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java
plugins/git4idea/src/git4idea/branch/GitBranchUtil.java
plugins/git4idea/src/git4idea/branch/GitBranchWorker.java
plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java
plugins/git4idea/src/git4idea/history/GitHistoryUtils.java
plugins/git4idea/src/git4idea/history/GitLogParser.java
plugins/git4idea/src/git4idea/history/GitLogRecord.java
plugins/git4idea/src/git4idea/log/GitLogProvider.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseProblemDetector.java
plugins/git4idea/src/git4idea/rebase/GitRebaser.java
plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java
plugins/git4idea/src/git4idea/repo/GitRepositoryManager.java
plugins/git4idea/src/git4idea/repo/GitRepositoryReader.java
plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java
plugins/git4idea/src/org/hanuna/gitalk/git/reader/util/GitException.java [new file with mode: 0644]
plugins/git4idea/testFramework/git4idea/test/GitTestRepositoryManager.groovy
plugins/git4idea/tests/git4idea/history/GitLogParserTest.java

index 6ec45a2f1a90b4c6b4a2f1760393d46d421e5fa2..899026f7f6a42d03d32e8e393aa57f27c7effa96 100644 (file)
@@ -43,7 +43,7 @@
       <module fileurl="file://$PROJECT_DIR$/xml/dom-impl/dom-impl.iml" filepath="$PROJECT_DIR$/xml/dom-impl/dom-impl.iml" group="xml" />
       <module fileurl="file://$PROJECT_DIR$/xml/dom-openapi/dom-openapi.iml" filepath="$PROJECT_DIR$/xml/dom-openapi/dom-openapi.iml" group="xml" />
       <module fileurl="file://$PROJECT_DIR$/xml/dom-tests/dom-tests.iml" filepath="$PROJECT_DIR$/xml/dom-tests/dom-tests.iml" group="xml" />
-      <module fileurl="file://$PROJECT_DIR$/platform/dvcs/dvcs.iml" filepath="$PROJECT_DIR$/platform/dvcs/dvcs.iml" group="platform" />
+      <module fileurl="file://$PROJECT_DIR$/platform/dvcs/dvcs.iml" filepath="$PROJECT_DIR$/platform/dvcs/dvcs.iml" group="platform/dvcs" />
       <module fileurl="file://$PROJECT_DIR$/plugins/eclipse/eclipse.iml" filepath="$PROJECT_DIR$/plugins/eclipse/eclipse.iml" group="plugins" />
       <module fileurl="file://$PROJECT_DIR$/plugins/eclipse/jps-plugin/eclipse-jps-plugin.iml" filepath="$PROJECT_DIR$/plugins/eclipse/jps-plugin/eclipse-jps-plugin.iml" group="plugins" />
       <module fileurl="file://$PROJECT_DIR$/java/execution/impl/execution-impl.iml" filepath="$PROJECT_DIR$/java/execution/impl/execution-impl.iml" group="java" />
       <module fileurl="file://$PROJECT_DIR$/platform/util-rt/util-rt.iml" filepath="$PROJECT_DIR$/platform/util-rt/util-rt.iml" group="platform" />
       <module fileurl="file://$PROJECT_DIR$/platform/vcs-api/vcs-api.iml" filepath="$PROJECT_DIR$/platform/vcs-api/vcs-api.iml" group="platform" />
       <module fileurl="file://$PROJECT_DIR$/platform/vcs-impl/vcs-impl.iml" filepath="$PROJECT_DIR$/platform/vcs-impl/vcs-impl.iml" group="platform" />
+      <module fileurl="file://$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-api/vcs-log-api.iml" filepath="$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-api/vcs-log-api.iml" group="platform/dvcs/vcs-log" />
+      <module fileurl="file://$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-common/vcs-log-common.iml" filepath="$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-common/vcs-log-common.iml" group="platform/dvcs/vcs-log" />
+      <module fileurl="file://$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-data/vcs-log-data.iml" filepath="$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-data/vcs-log-data.iml" group="platform/dvcs/vcs-log" />
+      <module fileurl="file://$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-graph/vcs-log-graph.iml" filepath="$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-graph/vcs-log-graph.iml" group="platform/dvcs/vcs-log" />
+      <module fileurl="file://$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-print-model/vcs-log-print-model.iml" filepath="$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-print-model/vcs-log-print-model.iml" group="platform/dvcs/vcs-log" />
+      <module fileurl="file://$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-ui/vcs-log-ui.iml" filepath="$PROJECT_DIR$/platform/dvcs/vcs-log/vcs-log-ui/vcs-log-ui.iml" group="platform/dvcs/vcs-log" />
       <module fileurl="file://$PROJECT_DIR$/platform/xdebugger-api/xdebugger-api.iml" filepath="$PROJECT_DIR$/platform/xdebugger-api/xdebugger-api.iml" group="platform" />
       <module fileurl="file://$PROJECT_DIR$/platform/xdebugger-impl/xdebugger-impl.iml" filepath="$PROJECT_DIR$/platform/xdebugger-impl/xdebugger-impl.iml" group="platform" />
       <module fileurl="file://$PROJECT_DIR$/xml/impl/xml.iml" filepath="$PROJECT_DIR$/xml/impl/xml.iml" group="xml" />
index 77c1b31af2d55b709a627ec4b4b5fd9b8ee0b09c..c13aeb0aa8b3122ebe65685194bbe68ed111f0cf 100644 (file)
@@ -13,6 +13,9 @@
     <orderEntry type="module" module-name="testFramework" />
     <orderEntry type="module" module-name="vcs-impl" />
     <orderEntry type="library" name="Guava" level="project" />
+    <orderEntry type="module" module-name="vcs-log-ui" />
+    <orderEntry type="module" module-name="vcs-log-api" />
+    <orderEntry type="module" module-name="vcs-log-data" />
   </component>
 </module>
 
diff --git a/platform/dvcs/src/META-INF/plugin.xml b/platform/dvcs/src/META-INF/plugin.xml
new file mode 100644 (file)
index 0000000..de43cf4
--- /dev/null
@@ -0,0 +1,9 @@
+<idea-plugin>
+  <name>DVCS Extensions</name>
+  <id>dvcs</id>
+
+  <extensions defaultExtensionNs="com.intellij">
+    <projectService serviceInterface="com.intellij.vcs.log.VcsLogSettings" serviceImplementation="com.intellij.vcs.log.VcsLogSettings"/>
+  </extensions>
+</idea-plugin>
+
diff --git a/platform/dvcs/src/com/intellij/dvcs/log/VcsLogManager.java b/platform/dvcs/src/com/intellij/dvcs/log/VcsLogManager.java
new file mode 100644 (file)
index 0000000..fa0d24c
--- /dev/null
@@ -0,0 +1,98 @@
+package com.intellij.dvcs.log;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.components.AbstractProjectComponent;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.project.DumbAwareRunnable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.startup.StartupManager;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.registry.Registry;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.changes.ui.ChangesViewContentI;
+import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.components.JBLoadingPanel;
+import com.intellij.ui.content.Content;
+import com.intellij.ui.content.impl.ContentImpl;
+import com.intellij.util.Consumer;
+import com.intellij.vcs.log.VcsLogProvider;
+import com.intellij.vcs.log.VcsLogRefresher;
+import org.hanuna.gitalk.data.VcsLogDataHolder;
+import org.hanuna.gitalk.ui.VcsLogUI;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author Kirill Likhodedov
+ */
+public class VcsLogManager extends AbstractProjectComponent {
+
+  public static final ExtensionPointName<VcsLogProvider> LOG_PROVIDER_EP = ExtensionPointName.create("com.intellij.logProvider");
+
+  @NotNull private final ProjectLevelVcsManager myVcsManager;
+
+  protected VcsLogManager(@NotNull Project project, @NotNull ProjectLevelVcsManager vcsManagerInitializedFirst) {
+    super(project);
+    myVcsManager = vcsManagerInitializedFirst;
+  }
+
+  @Override
+  public void initComponent() {
+    super.initComponent();
+
+    StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() {
+      @Override
+      public void run() {
+        if (!Registry.is("git.new.log")) {
+          return;
+        }
+
+        final VcsLogContainer mainPanel = new VcsLogContainer(myProject);
+        Content vcsLogContentPane = new ContentImpl(mainPanel, "Log", true);
+        ChangesViewContentI changesView = ChangesViewContentManager.getInstance(myProject);
+        changesView.addContent(vcsLogContentPane);
+        vcsLogContentPane.setCloseable(false);
+
+        // TODO multi-roots (including Git + Hg roots within a single project & multiple providers (or just pass root in params)
+        VcsLogProvider logProvider = null;
+        for (VcsLogProvider provider : Extensions.getExtensions(LOG_PROVIDER_EP, myProject)) {
+          logProvider = provider;
+        }
+        VirtualFile root = myVcsManager.getAllVcsRoots()[0].getPath();
+
+        VcsLogDataHolder.init(myProject, logProvider, root, new Consumer<VcsLogDataHolder>() {
+          @Override
+          public void consume(VcsLogDataHolder vcsLogDataHolder) {
+            Disposer.register(myProject, vcsLogDataHolder);
+            myProject.getMessageBus().connect(myProject).subscribe(VcsLogRefresher.TOPIC, vcsLogDataHolder);
+            VcsLogUI logUI = new VcsLogUI(vcsLogDataHolder, myProject);
+            mainPanel.init(logUI.getMainFrame().getMainComponent());
+          }
+        });
+
+      }
+    });
+  }
+
+  private static class VcsLogContainer extends JPanel {
+
+    private final JBLoadingPanel myLoadingPanel;
+
+    VcsLogContainer(@NotNull Disposable disposable) {
+      setLayout(new BorderLayout());
+      myLoadingPanel = new JBLoadingPanel(new BorderLayout(), disposable);
+      add(myLoadingPanel);
+      myLoadingPanel.startLoading();
+    }
+
+    void init(@NotNull JComponent mainComponent) {
+      myLoadingPanel.add(mainComponent);
+      myLoadingPanel.stopLoading();
+    }
+  }
+
+}
index 5d0305edea7a76e4c0c94eb98a8b87651227c68b..992654248e8338062817037450c51642ef50f91a 100644 (file)
@@ -15,6 +15,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
@@ -32,6 +33,7 @@ public abstract class AbstractRepositoryManager<T extends Repository> extends Ab
   @NotNull protected final Map<VirtualFile, T> myRepositories = new HashMap<VirtualFile, T>();
 
   @NotNull protected final ReentrantReadWriteLock REPO_LOCK = new ReentrantReadWriteLock();
+  @NotNull private final CountDownLatch myInitializationWaiter = new CountDownLatch(1);
 
   protected AbstractRepositoryManager(@NotNull Project project, @NotNull ProjectLevelVcsManager vcsManager, @NotNull AbstractVcs vcs,
                                       @NotNull String repoDirName) {
@@ -187,6 +189,7 @@ public abstract class AbstractRepositoryManager<T extends Repository> extends Ab
     try {
       myRepositories.clear();
       myRepositories.putAll(repositories);
+      myInitializationWaiter.countDown();
     }
     finally {
       REPO_LOCK.writeLock().unlock();
@@ -206,4 +209,15 @@ public abstract class AbstractRepositoryManager<T extends Repository> extends Ab
   public String toString() {
     return "RepositoryManager{myRepositories: " + myRepositories + '}';
   }
+
+  @Override
+  public void waitUntilInitialized() {
+    try {
+      myInitializationWaiter.await();
+    }
+    catch (InterruptedException e) {
+      LOG.error(e);
+    }
+  }
+
 }
index 9f0a5e2eedd858fe6493c24a45f42acab634a139..d27694cd05d0de7d4e620423ad749399d99be934 100644 (file)
@@ -64,4 +64,6 @@ public interface RepositoryManager<T extends Repository> {
   void updateRepository(VirtualFile root);
 
   void updateAllRepositories();
+
+  void waitUntilInitialized();
 }
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/CommitParents.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/CommitParents.java
new file mode 100644 (file)
index 0000000..7c054a5
--- /dev/null
@@ -0,0 +1,18 @@
+package com.intellij.vcs.log;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public interface CommitParents {
+
+  @NotNull
+  Hash getHash();
+
+  @NotNull
+  List<Hash> getParents();
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/Hash.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/Hash.java
new file mode 100644 (file)
index 0000000..2069239
--- /dev/null
@@ -0,0 +1,123 @@
+package com.intellij.vcs.log;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author erokhins
+ *         <code>
+ *         if (inputStr_1 == inputStr_2) {
+ *         Hash.build(inputStr_1) == Hash.build(inputStr_2)
+ *         }
+ *         </code>
+ */
+public final class Hash {
+
+  private static final Comparator<Hash> DEBUG_COMPARATOR = new Comparator<Hash>() {
+    @Override
+    public int compare(Hash o1, Hash o2) {
+      return o1.toStrHash().compareTo(o2.toStrHash());
+    }
+  };
+
+  private static final int SHORT_HASH_LENGTH = 7;
+  private static final int CAPABILITY = 5000;
+  private static final Map<Hash, Hash> ourCache = new HashMap<Hash, Hash>(CAPABILITY);
+
+  private static void clearMap() {
+    if (ourCache.size() >= CAPABILITY - 5) {
+      ourCache.clear();
+    }
+  }
+
+  @NotNull
+  public static Hash build(@NotNull String inputStr) {
+    clearMap();
+    byte[] data = buildData(inputStr);
+    Hash newHash = new Hash(data);
+    if (ourCache.containsKey(newHash)) {
+      return ourCache.get(newHash);
+    }
+    else {
+      ourCache.put(newHash, newHash);
+    }
+    return newHash;
+  }
+
+  @NotNull
+  private static byte[] buildData(@NotNull String inputStr) {
+    // if length == 5, need 3 byte + 1 signal byte
+    int length = inputStr.length();
+    byte even = (byte)(length % 2);
+    byte[] data = new byte[length / 2 + 1 + even];
+    data[0] = even;
+    try {
+      for (int i = 0; i < length / 2; i++) {
+        int k = Integer.parseInt(inputStr.substring(2 * i, 2 * i + 2), 16);
+        data[i + 1] = (byte)(k - 128);
+      }
+      if (even == 1) {
+        int k = Integer.parseInt(inputStr.substring(length - 1), 16);
+        data[length / 2 + 1] = (byte)(k - 128);
+      }
+    }
+    catch (NumberFormatException e) {
+      throw new IllegalArgumentException("bad hash string: " + inputStr);
+    }
+    return data;
+  }
+
+  @NotNull
+  private final byte[] data;
+  private final int hashCode;
+
+  private Hash(@NotNull byte[] hash) {
+    this.data = hash;
+    this.hashCode = Arrays.hashCode(hash);
+  }
+
+  public String toStrHash() {
+    assert data.length > 0 : "bad length Hash.data";
+    byte even = data[0];
+    StringBuilder sb = new StringBuilder();
+    for (int i = 1; i < data.length; i++) {
+      int k1 = (data[i] + 128) / 16;
+      int k2 = (data[i] + 128) % 16;
+      char c1 = Character.forDigit(k1, 16);
+      char c2 = Character.forDigit(k2, 16);
+      if (i == data.length - 1 && even == 1) {
+        sb.append(c2);
+      }
+      else {
+        sb.append(c1).append(c2);
+      }
+    }
+    return sb.toString();
+  }
+
+  public boolean equals(@Nullable Object obj) {
+    if (obj != null && obj.getClass() == Hash.class) {
+      Hash hash = (Hash)obj;
+      return hash.hashCode() == this.hashCode() && Arrays.equals(this.data, hash.data);
+    }
+    return false;
+  }
+
+  public int hashCode() {
+    return hashCode;
+  }
+
+  public String toString() {
+    return toStrHash();
+  }
+
+  public String toShortString() {
+    String s = toStrHash();
+    return s.substring(0, Math.min(s.length(), SHORT_HASH_LENGTH));
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/RebaseCommand.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/RebaseCommand.java
new file mode 100644 (file)
index 0000000..a46ff05
--- /dev/null
@@ -0,0 +1,44 @@
+package com.intellij.vcs.log;
+
+import com.intellij.vcs.log.Hash;
+import org.jetbrains.annotations.Nullable;
+
+public class RebaseCommand {
+  public enum RebaseCommandKind {
+    PICK,
+    FIXUP,
+    REWORD
+  }
+
+  private final RebaseCommandKind kind;
+  private final Hash commit;
+  private final String newMessage;
+
+  public RebaseCommand(RebaseCommandKind kind, Hash commit) {
+    this(kind, commit, null);
+  }
+
+  public RebaseCommand(RebaseCommandKind kind, Hash commit, @Nullable String newMessage) {
+    this.kind = kind;
+    this.commit = commit;
+    this.newMessage = newMessage;
+  }
+
+  public RebaseCommandKind getKind() {
+    return kind;
+  }
+
+  public Hash getCommit() {
+    return commit;
+  }
+
+  @Nullable
+  public String getNewMessage() {
+    return newMessage;
+  }
+
+  @Override
+  public String toString() {
+    return kind + " " + getCommit().toStrHash() + (newMessage == null ? "" : " " + newMessage);
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/Ref.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/Ref.java
new file mode 100644 (file)
index 0000000..69eae85
--- /dev/null
@@ -0,0 +1,90 @@
+package com.intellij.vcs.log;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public final class Ref {
+  private final Hash commitHash;
+  private final String name;
+  private final RefType type;
+
+  public Ref(@NotNull Hash commitHash, @NotNull String name, @NotNull RefType type) {
+    this.commitHash = commitHash;
+    this.name = name;
+    this.type = type;
+
+  }
+
+  @NotNull
+  public RefType getType() {
+    return type;
+  }
+
+  @NotNull
+  public Hash getCommitHash() {
+    return commitHash;
+  }
+
+  @NotNull
+  public String getName() {
+    return name;
+  }
+
+  @NotNull
+  public String getShortName() {
+    int ind = name.lastIndexOf("/");
+    return name.substring(ind + 1);
+  }
+
+  @Override
+  public String toString() {
+    return "Ref{" +
+           "commitHash=" + commitHash +
+           ", name='" + name + '\'' +
+           ", type=" + type +
+           '}';
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    Ref ref = (Ref)o;
+
+    if (commitHash != null ? !commitHash.equals(ref.commitHash) : ref.commitHash != null) return false;
+    if (name != null ? !name.equals(ref.name) : ref.name != null) return false;
+    if (type != ref.type) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = commitHash != null ? commitHash.hashCode() : 0;
+    result = 31 * result + (name != null ? name.hashCode() : 0);
+    result = 31 * result + (type != null ? type.hashCode() : 0);
+    return result;
+  }
+
+  public enum RefType {
+    LOCAL_BRANCH,
+    BRANCH_UNDER_INTERACTIVE_REBASE,
+    REMOTE_BRANCH,
+    TAG,
+    STASH,
+    ANOTHER,
+    HEAD;
+
+    public boolean isBranch() {
+      return this == LOCAL_BRANCH || this == BRANCH_UNDER_INTERACTIVE_REBASE || this == REMOTE_BRANCH || this == HEAD;
+    }
+
+    public boolean isLocalOrHead() {
+      return this == LOCAL_BRANCH || this == HEAD;
+    }
+
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/SimpleCommitParents.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/SimpleCommitParents.java
new file mode 100644 (file)
index 0000000..84196cd
--- /dev/null
@@ -0,0 +1,30 @@
+package com.intellij.vcs.log;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class SimpleCommitParents implements CommitParents {
+  private final Hash commitHash;
+  private final List<Hash> parentHashes;
+
+  public SimpleCommitParents(Hash commitHash, List<Hash> parentHashes) {
+    this.commitHash = commitHash;
+    this.parentHashes = parentHashes;
+  }
+
+  @NotNull
+  @Override
+  public Hash getHash() {
+    return commitHash;
+  }
+
+  @NotNull
+  @Override
+  public List<Hash> getParents() {
+    return parentHashes;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsCommit.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsCommit.java
new file mode 100644 (file)
index 0000000..5166148
--- /dev/null
@@ -0,0 +1,23 @@
+package com.intellij.vcs.log;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ *
+ * @author Kirill Likhodedov
+ */
+public interface VcsCommit extends CommitParents {
+
+  @Override
+  @NotNull
+  Hash getHash();
+
+  @NotNull
+  String getSubject();
+
+  @NotNull
+  String getAuthorName();
+
+  long getAuthorTime();
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsCommitDetails.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsCommitDetails.java
new file mode 100644 (file)
index 0000000..9626f9b
--- /dev/null
@@ -0,0 +1,32 @@
+package com.intellij.vcs.log;
+
+import com.intellij.openapi.vcs.changes.Change;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * Full details of a commit: all metadata (commit message, author, committer, etc.) and the changes.
+ *
+ * @author Kirill Likhodedov
+ */
+public interface VcsCommitDetails extends VcsCommit {
+
+  @NotNull
+  String getFullMessage();
+
+  @NotNull
+  String getAuthorEmail();
+
+  @NotNull
+  String getCommitterName();
+
+  @NotNull
+  String getCommitterEmail();
+
+  long getCommitTime();
+
+  @NotNull
+  Collection<Change> getChanges();
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsCommitImpl.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsCommitImpl.java
new file mode 100644 (file)
index 0000000..6b9c52f
--- /dev/null
@@ -0,0 +1,55 @@
+package com.intellij.vcs.log;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * Simple implementation of the {@link VcsCommit}: a container for all necessary fields.
+ *
+ * @author Kirill Likhodedov
+ */
+public class VcsCommitImpl implements VcsCommit {
+  @NotNull private final Hash myHash;
+  @NotNull private final List<Hash> myParents;
+  @NotNull private final String mySubject;
+  @NotNull private final String myAuthorName;
+  private final long myAuthorTime;
+
+  public VcsCommitImpl(@NotNull Hash hash, @NotNull List<Hash> parents, @NotNull String subject, @NotNull String author, long time) {
+    myHash = hash;
+    myParents = parents;
+    mySubject = subject;
+    myAuthorName = author;
+    myAuthorTime = time;
+  }
+
+  @NotNull
+  @Override
+  public Hash getHash() {
+    return myHash;
+  }
+
+  @NotNull
+  @Override
+  public List<Hash> getParents() {
+    return myParents;
+  }
+
+  @NotNull
+  @Override
+  public String getSubject() {
+    return mySubject;
+  }
+
+  @NotNull
+  @Override
+  public String getAuthorName() {
+    return myAuthorName;
+  }
+
+  @Override
+  public long getAuthorTime() {
+    return myAuthorTime;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsLogProvider.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsLogProvider.java
new file mode 100644 (file)
index 0000000..ba332f0
--- /dev/null
@@ -0,0 +1,48 @@
+package com.intellij.vcs.log;
+
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Provides the information needed to build the VCS log, such as the list of most recent commits with their parents.
+ *
+ * @author Kirill Likhodedov
+ */
+public interface VcsLogProvider {
+
+  int COMMIT_BLOCK_SIZE = 1000;
+
+  /**
+   * Reads {@link #COMMIT_BLOCK_SIZE the first part} of the log.
+   */
+  @NotNull
+  List<? extends VcsCommitDetails> readFirstBlock(@NotNull VirtualFile root, boolean ordered) throws VcsException;
+
+  /**
+   * Reads the whole history, but only hashes & parents.
+   */
+  @NotNull
+  List<CommitParents> readAllHashes(@NotNull VirtualFile root) throws VcsException;
+
+  /**
+   * Reads those details of the given commits, which are necessary to be shown in the log table.
+   */
+  @NotNull
+  List<? extends VcsCommit> readMiniDetails(@NotNull VirtualFile root, @NotNull List<String> hashes) throws VcsException;
+
+  /**
+   * Read full details of the given commits from the VCS.
+   */
+  @NotNull
+  List<? extends VcsCommitDetails> readDetails(@NotNull VirtualFile root, @NotNull List<String> hashes) throws VcsException;
+
+  /**
+   * Read all references (branches, tags, etc.) for the given roots.
+   */
+  Collection<Ref> readAllRefs(@NotNull VirtualFile root) throws VcsException;
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsLogRefresher.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsLogRefresher.java
new file mode 100644 (file)
index 0000000..18e8d04
--- /dev/null
@@ -0,0 +1,33 @@
+package com.intellij.vcs.log;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.messages.Topic;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Refreshes the VCS Log, completely, or partly.
+ *
+ * @author Kirill Likhodedov
+ */
+public interface VcsLogRefresher {
+
+  Topic<VcsLogRefresher> TOPIC = Topic.create(VcsLogRefresher.class.getName(), VcsLogRefresher.class);
+
+  /**
+   * Makes the log perform complete refresh for all roots.
+   * It retrieves the data from the VCS and rebuilds the whole log.
+   */
+  void refreshCompletely();
+
+  /**
+   * Makes the log perform refresh for the given root.
+   * This refresh can be optimized, i. e. it can query VCS just for the part of the log.
+   */
+  void refresh(@NotNull VirtualFile root);
+
+  /**
+   * Makes the log refresh only the reference labels for the given root.
+   */
+  void refreshRefs(@NotNull VirtualFile root);
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsLogSettings.java b/platform/dvcs/vcs-log/vcs-log-api/src/com/intellij/vcs/log/VcsLogSettings.java
new file mode 100644 (file)
index 0000000..c2aa268
--- /dev/null
@@ -0,0 +1,40 @@
+package com.intellij.vcs.log;
+
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Kirill Likhodedov
+ */
+@State(name = "Vcs.Log.Settings", storages = {@Storage(file = StoragePathMacros.WORKSPACE_FILE)})
+public class VcsLogSettings implements PersistentStateComponent<VcsLogSettings.State> {
+
+  private State myState = new State();
+
+  public static class State {
+    public boolean SHOW_DETAILS = false;
+  }
+
+  @Nullable
+  @Override
+  public State getState() {
+    return myState;
+  }
+
+  @Override
+  public void loadState(State state) {
+    myState = state;
+  }
+
+  public boolean isShowDetails() {
+    return myState.SHOW_DETAILS;
+  }
+
+  public void setShowDetails(boolean showDetails) {
+    myState.SHOW_DETAILS = showDetails;
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/test/com/intellij/vcs/log/HashBuildTests.java b/platform/dvcs/vcs-log/vcs-log-api/test/com/intellij/vcs/log/HashBuildTests.java
new file mode 100644 (file)
index 0000000..c7e9060
--- /dev/null
@@ -0,0 +1,62 @@
+package com.intellij.vcs.log;
+
+
+import junit.framework.Assert;
+import org.junit.Test;
+
+/**
+ * @author erokhins
+ */
+public class HashBuildTests {
+  public void runStringTest(String strHash) {
+    Hash hash = Hash.build(strHash);
+    Assert.assertEquals(strHash, hash.toStrHash());
+  }
+
+  @Test
+  public void testBuildNone() throws Exception {
+    runStringTest("");
+  }
+
+  @Test
+  public void testBuild0() throws Exception {
+    runStringTest("0");
+  }
+
+  @Test
+  public void testBuild000() throws Exception {
+    runStringTest("0000");
+  }
+
+  @Test
+  public void testBuild1() throws Exception {
+    runStringTest("1");
+  }
+
+  @Test
+  public void testBuildSomething() throws Exception {
+    runStringTest("ff01a");
+  }
+
+  @Test
+  public void testBuildEven() throws Exception {
+    runStringTest("1133");
+  }
+
+  @Test
+  public void testBuildOdd() throws Exception {
+    runStringTest("ffa");
+  }
+
+  @Test
+  public void testBuildLongOdd() throws Exception {
+    runStringTest("ff01a123125afabcdef12345678900987654321");
+  }
+
+  @Test
+  public void testBuildLongEven() throws Exception {
+    runStringTest("ff01a123125afabcdef123456789009876543219");
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/test/com/intellij/vcs/log/HashEqualsTests.java b/platform/dvcs/vcs-log/vcs-log-api/test/com/intellij/vcs/log/HashEqualsTests.java
new file mode 100644 (file)
index 0000000..3c1b372
--- /dev/null
@@ -0,0 +1,38 @@
+package com.intellij.vcs.log;
+
+import com.intellij.vcs.log.Hash;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author erokhins
+ */
+public class HashEqualsTests {
+
+  @Test
+  public void testEqualsSelf() throws Exception {
+    Hash hash1 = Hash.build("adf");
+    Assert.assertTrue(hash1.equals(hash1));
+  }
+
+  @Test
+  public void testEqualsNull() throws Exception {
+    Hash hash1 = Hash.build("adf");
+    Assert.assertFalse(hash1.equals(null));
+  }
+
+  @Test
+  public void testEquals() throws Exception {
+    Hash hash1 = Hash.build("adf");
+    Hash hash2 = Hash.build("adf");
+    Assert.assertTrue(hash1.equals(hash2));
+  }
+
+  @Test
+  public void testEqualsNone() throws Exception {
+    Hash hash1 = Hash.build("");
+    Hash hash2 = Hash.build("");
+    Assert.assertTrue(hash1.equals(hash2));
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-api/vcs-log-api.iml b/platform/dvcs/vcs-log/vcs-log-api/vcs-log-api.iml
new file mode 100644 (file)
index 0000000..9cfc4a7
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/lib/annotations.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="util-rt" />
+    <orderEntry type="module" module-name="extensions" />
+    <orderEntry type="module" module-name="core-api" />
+    <orderEntry type="module" module-name="vcs-api" />
+    <orderEntry type="library" name="JUnit4" level="project" />
+  </component>
+</module>
+
diff --git a/platform/dvcs/vcs-log/vcs-log-common/src/META-INF/MANIFEST.MF b/platform/dvcs/vcs-log/vcs-log-common/src/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..bfa8fdb
--- /dev/null
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: org.hanuna.gitalk.main.Main
+
diff --git a/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/MyTimer.java b/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/MyTimer.java
new file mode 100644 (file)
index 0000000..9d17d8b
--- /dev/null
@@ -0,0 +1,35 @@
+package org.hanuna.gitalk.common;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public class MyTimer {
+  private long timestamp = System.currentTimeMillis();
+  private String message = "timer:";
+
+  public MyTimer() {
+  }
+
+  public MyTimer(@NotNull String message) {
+    this.message = message;
+  }
+
+  public void clear() {
+    timestamp = System.currentTimeMillis();
+  }
+
+  public void clear(@NotNull String message) {
+    this.message = message;
+  }
+
+  public long get() {
+    return System.currentTimeMillis() - timestamp;
+  }
+
+  public void print() {
+    long ms = get();
+    System.out.println(message + ":" + ms);
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/CompressedList.java b/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/CompressedList.java
new file mode 100644 (file)
index 0000000..e91282e
--- /dev/null
@@ -0,0 +1,17 @@
+package org.hanuna.gitalk.common.compressedlist;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public interface CompressedList<T> {
+
+  // unmodifiableList
+  @NotNull
+  public List<T> getList();
+
+  public void recalculate(@NotNull UpdateRequest updateRequest);
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/NoCompressedList.java b/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/NoCompressedList.java
new file mode 100644 (file)
index 0000000..9e21111
--- /dev/null
@@ -0,0 +1,55 @@
+package org.hanuna.gitalk.common.compressedlist;
+
+import org.hanuna.gitalk.common.compressedlist.generator.Generator;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class NoCompressedList<T> implements CompressedList<T> {
+  private final List<T> calcList;
+  private final Generator<T> generator;
+  private int size;
+
+  public NoCompressedList(Generator<T> generator, int size) {
+    assert size >= 0 : "bad size";
+    calcList = new ArrayList<T>(size);
+    this.generator = generator;
+    this.size = size;
+    generate();
+  }
+
+  private void generate() {
+    calcList.clear();
+    T t = generator.generateFirst();
+    calcList.add(t);
+    for (int i = 1; i < size; i++) {
+      t = generator.generate(t, 1);
+      calcList.add(t);
+    }
+  }
+
+  @NotNull
+  @Override
+  public List<T> getList() {
+    return Collections.unmodifiableList(calcList);
+  }
+
+
+  @Override
+  public void recalculate(@NotNull UpdateRequest updateRequest) {
+    if (updateRequest == UpdateRequest.ID_UpdateRequest) {
+      return;
+    }
+    if (updateRequest.to() >= size) {
+      throw new IllegalArgumentException("Bad updateRequest: " + updateRequest.from() + ", " +
+                                         +updateRequest.to() + ", " + updateRequest.addedElementCount());
+    }
+    size = updateRequest.addedElementCount() - updateRequest.removedElementCount() + size;
+    generate();
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/RuntimeGenerateCompressedList.java b/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/RuntimeGenerateCompressedList.java
new file mode 100644 (file)
index 0000000..197e921
--- /dev/null
@@ -0,0 +1,183 @@
+package org.hanuna.gitalk.common.compressedlist;
+
+import com.intellij.util.containers.SLRUCache;
+import org.hanuna.gitalk.common.compressedlist.generator.Generator;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author erokhins
+ *         postion is position in CompressedList
+ *         index is index positionElement in positionItems
+ */
+public class RuntimeGenerateCompressedList<T> implements CompressedList<T> {
+  private final SLRUCache<Integer, T> cache = new SLRUCache<Integer, T>(200, 200) {
+
+    @NotNull
+    @Override
+    public T createValue(Integer key) {
+      return RuntimeGenerateCompressedList.this.get(key);
+    }
+  };
+
+  private final Generator<T> generator;
+  private final int intervalSave;
+  private final List<PositionItem> positionItems = new ArrayList<PositionItem>();
+  private int size;
+
+  public RuntimeGenerateCompressedList(Generator<T> generator, int size, int intervalSave) {
+    this.generator = generator;
+    this.intervalSave = intervalSave;
+    this.size = size;
+    T firstT = generator.generateFirst();
+    positionItems.add(new PositionItem(0, firstT));
+    int curPosition = intervalSave;
+    T prevT = firstT;
+    while (curPosition < size) {
+      prevT = generator.generate(prevT, intervalSave);
+      positionItems.add(new PositionItem(curPosition, prevT));
+      curPosition = curPosition + intervalSave;
+    }
+  }
+
+  public RuntimeGenerateCompressedList(Generator<T> generator, int size) {
+    this(generator, size, 20);
+  }
+
+  // returned index k, which is max from k: positionItems.get(k).getPosition() <= position
+  private int binarySearch(int position) {
+    assert positionItems.size() > 0;
+    int x = 0;
+    int y = positionItems.size() - 1;
+    while (y - x > 1) {
+      int z = (x + y) / 2;
+      if (positionItems.get(z).getPosition() <= position) {
+        x = z;
+      }
+      else {
+        y = z;
+      }
+    }
+    if (positionItems.get(y).getPosition() <= position) {
+      return y;
+    }
+    return x;
+  }
+
+  @NotNull
+  @Override
+  public List<T> getList() {
+    return new AbstractList<T>() {
+      @Override
+      public T get(int index) {
+        return cache.get(index);
+      }
+
+      @Override
+      public int size() {
+        return size;
+      }
+    };
+  }
+
+
+  private void fixPositionsTail(int startIndex, int deltaSize) {
+    for (int i = startIndex; i < positionItems.size(); i++) {
+      PositionItem positionItem = positionItems.get(i);
+      positionItem.setPosition(positionItem.getPosition() + deltaSize);
+    }
+  }
+
+  private List<PositionItem> regenerateMediate(PositionItem prevSavePositionItem, int downSavePosition) {
+    List<PositionItem> mediateSave = new ArrayList<PositionItem>();
+    T prevT = prevSavePositionItem.getT();
+    int curTPosition = prevSavePositionItem.getPosition() + intervalSave;
+
+    while (curTPosition < downSavePosition - intervalSave) {
+      prevT = generator.generate(prevT, intervalSave);
+      mediateSave.add(new PositionItem(curTPosition, prevT));
+      curTPosition = curTPosition + intervalSave;
+    }
+    return mediateSave;
+  }
+
+  private void checkReplace(UpdateRequest updateRequest) {
+    if (updateRequest.to() >= size) {
+      throw new IllegalArgumentException("size= " + size + "Bad updateRequest: " + updateRequest);
+    }
+  }
+
+  @Override
+  public void recalculate(@NotNull UpdateRequest updateRequest) {
+    if (updateRequest == UpdateRequest.ID_UpdateRequest) {
+      return;
+    }
+    checkReplace(updateRequest);
+    cache.clear();
+    int deltaSize = updateRequest.addedElementCount() - updateRequest.removedElementCount();
+
+    int upSaveIndex = binarySearch(updateRequest.from());
+    if (upSaveIndex > 0) { // update started from updateRequest.from()
+      upSaveIndex--;
+    }
+    else {
+      positionItems.set(0, new PositionItem(0, generator.generateFirst()));
+    }
+    PositionItem upSavePositionItem = positionItems.get(upSaveIndex);
+
+    int downSaveIndex = upSaveIndex;
+    while (downSaveIndex < positionItems.size() && positionItems.get(downSaveIndex).getPosition() <= updateRequest.to()) {
+      downSaveIndex++;
+    }
+
+    size = size + deltaSize;
+    fixPositionsTail(downSaveIndex, deltaSize);
+
+    int downSavePosition = size;
+    if (downSaveIndex < positionItems.size()) {
+      downSavePosition = positionItems.get(downSaveIndex).getPosition();
+    }
+    List<PositionItem> mediate = regenerateMediate(upSavePositionItem, downSavePosition);
+
+    positionItems.subList(upSaveIndex + 1, downSaveIndex).clear();
+    positionItems.addAll(upSaveIndex + 1, mediate);
+  }
+
+  @NotNull
+  private T get(int position) {
+    if (position < 0 || position >= size) {
+      throw new IllegalArgumentException();
+    }
+    int saveIndex = binarySearch(position);
+    final PositionItem positionItem = positionItems.get(saveIndex);
+    assert position >= positionItem.getPosition();
+    return generator.generate(positionItem.getT(), position - positionItem.getPosition());
+  }
+
+  private class PositionItem {
+    private int position;
+    private final T t;
+
+    private PositionItem(int position, @NotNull T t) {
+      this.position = position;
+      this.t = t;
+    }
+
+    public int getPosition() {
+      return position;
+    }
+
+    @NotNull
+    public T getT() {
+      return t;
+    }
+
+    public void setPosition(int position) {
+      this.position = position;
+    }
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/UpdateRequest.java b/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/UpdateRequest.java
new file mode 100644 (file)
index 0000000..2a245ff
--- /dev/null
@@ -0,0 +1,67 @@
+package org.hanuna.gitalk.common.compressedlist;
+
+/**
+ * @author erokhins
+ */
+public class UpdateRequest {
+  public static UpdateRequest ID_UpdateRequest = new UpdateRequest(0, 0, 1);
+
+  /**
+   * This class describe replace in list or another ordered set's
+   * Elements [from, to] will be remove, and add addedElementCount new's element in this interval
+   * Elements from and to should be exist in list.
+   * Elements from - 1, to + 1, if they exist, shouldn't change after this replace
+   * <p/>
+   * Example:
+   * UpdateRequest(1, 3, 2)
+   * before:   10, 20, 30, 40, 50, ...
+   * after:    10, 20,  1,  2, 40, 50, ...
+   */
+
+  public static UpdateRequest buildFromToInterval(int oldFrom, int oldTo, int newFrom, int newTo) {
+    if (oldFrom != newFrom || oldFrom > oldTo || newFrom > newTo) {
+      throw new IllegalArgumentException("oldFrom: " + oldFrom + ", oldTo: " + oldTo +
+                                         ", newFrom: " + newFrom + ", newTo: " + newTo);
+    }
+    return new UpdateRequest(oldFrom, oldTo, newTo - newFrom + 1);
+  }
+
+  private final int from;
+  private final int to;
+  private final int addedElementCount;
+
+  public UpdateRequest(int from, int to, int addedElementCount) {
+    if (from < 0 || from > to || addedElementCount < 0) {
+      throw new IllegalArgumentException("from: " + from + "to: " + to +
+                                         "addedElementCount: " + addedElementCount);
+    }
+    this.from = from;
+    this.to = to;
+    this.addedElementCount = addedElementCount;
+  }
+
+  public int from() {
+    return from;
+  }
+
+  public int to() {
+    return to;
+  }
+
+  public int addedElementCount() {
+    return addedElementCount;
+  }
+
+  public int removedElementCount() {
+    return to - from + 1;
+  }
+
+  @Override
+  public String toString() {
+    return "UpdateRequest{" +
+           "from=" + from +
+           ", to=" + to +
+           ", addedElementCount=" + addedElementCount +
+           '}';
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/VcsLogLogger.java b/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/VcsLogLogger.java
new file mode 100644 (file)
index 0000000..5b6527b
--- /dev/null
@@ -0,0 +1,12 @@
+package org.hanuna.gitalk.common.compressedlist;
+
+import com.intellij.openapi.diagnostic.Logger;
+
+/**
+ * @author Kirill Likhodedov
+ */
+public class VcsLogLogger {
+
+  public static final Logger LOG = Logger.getInstance("Vcs.Log");
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/generator/AbstractGenerator.java b/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/generator/AbstractGenerator.java
new file mode 100644 (file)
index 0000000..2e5fbed
--- /dev/null
@@ -0,0 +1,36 @@
+package org.hanuna.gitalk.common.compressedlist.generator;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public abstract class AbstractGenerator<T, M extends T> implements Generator<T> {
+
+
+  @NotNull
+  @Override
+  public T generate(@NotNull T prev, int steps) {
+    if (steps < 0) {
+      throw new IllegalStateException("bad steps: " + steps);
+    }
+    if (steps == 0) {
+      return prev;
+    }
+    M row = this.createMutable(prev);
+    for (int i = 0; i < steps; i++) {
+      row = oneStep(row);
+    }
+    return row;
+  }
+
+  @NotNull
+  protected abstract M createMutable(@NotNull T t);
+
+  /**
+   * @throws java.util.NoSuchElementException
+   *
+   */
+  @NotNull
+  protected abstract M oneStep(@NotNull M row);
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/generator/Generator.java b/platform/dvcs/vcs-log/vcs-log-common/src/org/hanuna/gitalk/common/compressedlist/generator/Generator.java
new file mode 100644 (file)
index 0000000..c96e00e
--- /dev/null
@@ -0,0 +1,19 @@
+package org.hanuna.gitalk.common.compressedlist.generator;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public interface Generator<T> {
+  /**
+   * @throws java.util.NoSuchElementException
+   *
+   * @throws IllegalArgumentException
+   */
+  @NotNull
+  public T generate(@NotNull T prev, int steps);
+
+  @NotNull
+  public T generateFirst();
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/test/org/hanuna/gitalk/common/compressedlist/IntegerCompressedListTest.java b/platform/dvcs/vcs-log/vcs-log-common/test/org/hanuna/gitalk/common/compressedlist/IntegerCompressedListTest.java
new file mode 100644 (file)
index 0000000..bdd01fb
--- /dev/null
@@ -0,0 +1,96 @@
+package org.hanuna.gitalk.common.compressedlist;
+
+import org.hanuna.gitalk.common.compressedlist.generator.Generator;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * @author erokhins
+ */
+public class IntegerCompressedListTest {
+  private final int size = 200;
+  private final IntegerGenerator generator = new IntegerGenerator(size);
+
+  private final UpdateRequest[] simple = {new UpdateRequest(1, 5, 1), new UpdateRequest(1, 5, 10), new UpdateRequest(10, 15, 97)};
+
+  private final UpdateRequest[] marginal =
+    {new UpdateRequest(size - 2, size - 1, 0), new UpdateRequest(size - 4, size - 3, 100), new UpdateRequest(0, 1, 0),
+      new UpdateRequest(0, 1, 100), new UpdateRequest(10, 15, 97)};
+
+  @Test
+  public void simpleNoCompressedList() {
+    runTests(new NoCompressedList<Integer>(generator, size), simple);
+  }
+
+  @Test
+  public void simpleRuntimeGenerateCompressedList() {
+    runTests(new RuntimeGenerateCompressedList<Integer>(generator, size), simple);
+  }
+
+
+  @Test
+  public void marginalNoCompressedList() {
+    runTests(new NoCompressedList<Integer>(generator, size), marginal);
+  }
+
+  @Test
+  public void marginalRuntimeGenerateCompressedList() {
+    runTests(new RuntimeGenerateCompressedList<Integer>(generator, size), marginal);
+  }
+
+
+  public void runTests(CompressedList<Integer> list, UpdateRequest[] updateRequests) {
+    RunCompressedListTest<Integer> runner = new RunCompressedListTest<Integer>(list, generator);
+    runner.assertList();
+
+    for (UpdateRequest updateRequest : updateRequests) {
+      generator.replace(updateRequest);
+      runner.runReplace(updateRequest);
+    }
+  }
+
+
+  private class IntegerGenerator implements Generator<Integer> {
+    private final List<Integer> list;
+    private int replaceCount = 0;
+
+    public IntegerGenerator(int size) {
+      this.list = new ArrayList<Integer>(size);
+      for (int i = 0; i < size; i++) {
+        list.add(i);
+      }
+    }
+
+    @NotNull
+    @Override
+    public Integer generate(@NotNull Integer prev, int steps) {
+      int prevIndex = list.indexOf(prev);
+      assert prevIndex != -1;
+      if (prevIndex + steps >= list.size()) {
+        throw new NoSuchElementException();
+      }
+      return list.get(prevIndex + steps);
+    }
+
+    @NotNull
+    @Override
+    public Integer generateFirst() {
+      return list.get(0);
+    }
+
+    public void replace(UpdateRequest updateRequest) {
+      replaceCount++;
+      int shift = replaceCount * 1000;
+      list.subList(updateRequest.from(), updateRequest.to() + 1).clear();
+      List<Integer> newLists = new ArrayList<Integer>(updateRequest.addedElementCount());
+      for (int i = 0; i < updateRequest.addedElementCount(); i++) {
+        newLists.add(shift + i);
+      }
+      list.addAll(updateRequest.from(), newLists);
+    }
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/test/org/hanuna/gitalk/common/compressedlist/RunCompressedListTest.java b/platform/dvcs/vcs-log/vcs-log-common/test/org/hanuna/gitalk/common/compressedlist/RunCompressedListTest.java
new file mode 100644 (file)
index 0000000..a9aa29c
--- /dev/null
@@ -0,0 +1,62 @@
+package org.hanuna.gitalk.common.compressedlist;
+
+import org.hanuna.gitalk.common.compressedlist.generator.Generator;
+
+import static junit.framework.Assert.assertEquals;
+
+/**
+ * @author erokhins
+ */
+public class RunCompressedListTest<T> {
+  private final CompressedList<T> compressedList;
+  private final Generator<T> generator;
+
+  public RunCompressedListTest(CompressedList<T> compressedList, Generator<T> generator) {
+    this.compressedList = compressedList;
+    this.generator = generator;
+  }
+
+  private String CompressedListStr() {
+    StringBuilder s = new StringBuilder();
+    for (int i = 0; i < compressedList.getList().size(); i++) {
+      T listElement = compressedList.getList().get(i);
+      s.append(listElement).append(" ");
+    }
+    return s.toString();
+  }
+
+  private String ActualListStr() {
+    StringBuilder s = new StringBuilder();
+    T t = generator.generateFirst();
+    s.append(t).append(" ");
+    for (int i = 1; i < compressedList.getList().size(); i++) {
+      t = generator.generate(t, 1);
+      s.append(t).append(" ");
+    }
+    return s.toString();
+  }
+
+  public void assertList() {
+    assertList("");
+  }
+
+  public void assertList(String message) {
+    assertEquals(message, ActualListStr(), CompressedListStr());
+  }
+
+  public void runReplace(UpdateRequest updateRequest) {
+    compressedList.recalculate(updateRequest);
+    assertList(replaceToStr(updateRequest));
+  }
+
+
+  public String replaceToStr(UpdateRequest updateRequest) {
+    return "UpdateRequest: from: " +
+           updateRequest.from() +
+           ", to: " +
+           updateRequest.to() +
+           ", addedElementCounts: " +
+           updateRequest.addedElementCount();
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-common/vcs-log-common.iml b/platform/dvcs/vcs-log/vcs-log-common/vcs-log-common.iml
new file mode 100644 (file)
index 0000000..c7336ab
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library" exported="">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/lib/annotations.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" exported="" scope="TEST">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/lib/junit-4.10.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="vcs-log-api" scope="TEST" />
+    <orderEntry type="module" module-name="util-rt" />
+    <orderEntry type="module" module-name="util" />
+  </component>
+</module>
+
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/CommitDetailsGetter.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/CommitDetailsGetter.java
new file mode 100644 (file)
index 0000000..42ab586
--- /dev/null
@@ -0,0 +1,26 @@
+package org.hanuna.gitalk.data;
+
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.vcs.log.VcsCommitDetails;
+import com.intellij.vcs.log.VcsLogProvider;
+
+import java.util.List;
+
+/**
+ * The CommitDetailsGetter is responsible for getting {@link VcsCommitDetails complete commit details} from the cache or from the VCS.
+ *
+ * @author Kirill Likhodedov
+ */
+public class CommitDetailsGetter extends DataGetter<VcsCommitDetails> {
+
+  CommitDetailsGetter(VcsLogDataHolder dataHolder, VcsLogProvider logProvider, VirtualFile root) {
+    super(dataHolder, logProvider, root, new VcsCommitCache<VcsCommitDetails>());
+  }
+
+  @Override
+  protected List<? extends VcsCommitDetails> readDetails(List<String> hashes) throws VcsException {
+    return myLogProvider.readDetails(myRoot, hashes);
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/DataGetter.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/DataGetter.java
new file mode 100644 (file)
index 0000000..244a0a2
--- /dev/null
@@ -0,0 +1,179 @@
+package org.hanuna.gitalk.data;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Consumer;
+import com.intellij.util.Function;
+import com.intellij.util.concurrency.QueueProcessor;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.vcs.log.CommitParents;
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.VcsLogProvider;
+import org.hanuna.gitalk.common.compressedlist.VcsLogLogger;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * The DataGetter realizes the following pattern of getting some data (parametrized by {@code T}) from the VCS:
+ * <ul>
+ *   <li>it tries to get it from the cache;</li>
+ *   <li>if it fails, it tries to get it from the VCS, and additionally loads several commits around the requested one,
+ *       to avoid querying the VCS if user investigates details of nearby commits.</li>
+ *   <li>The loading happens asynchronously: a fake {@link LoadingDetails} object is returned </li>
+ * </ul>
+ *
+ * @author Kirill Likhodedov
+ */
+public abstract class DataGetter<T extends CommitParents> implements Disposable {
+
+  private static final Logger LOG = VcsLogLogger.LOG;
+  private static final int UP_PRELOAD_COUNT = 20;
+  private static final int DOWN_PRELOAD_COUNT = 40;
+
+  @NotNull protected final VcsLogDataHolder myDataHolder;
+  @NotNull protected final VcsLogProvider myLogProvider;
+  @NotNull protected final VirtualFile myRoot;
+  @NotNull private final VcsCommitCache<T> myCache;
+
+  @NotNull private final QueueProcessor<TaskDescriptor> myLoader = new QueueProcessor<TaskDescriptor>(new DetailsLoadingTask());
+  @NotNull private final Collection<Runnable> myLoadingFinishedListeners = new ArrayList<Runnable>();
+
+  DataGetter(@NotNull VcsLogDataHolder dataHolder, @NotNull VcsLogProvider logProvider, @NotNull VirtualFile root,
+             @NotNull VcsCommitCache<T> cache) {
+    myDataHolder = dataHolder;
+    myLogProvider = logProvider;
+    myRoot = root;
+    myCache = cache;
+    Disposer.register(dataHolder, this);
+  }
+
+  @Override
+  public void dispose() {
+    myLoadingFinishedListeners.clear();
+    myLoader.clear();
+  }
+
+  @NotNull
+  public T getCommitData(final Node node) {
+    assert EventQueue.isDispatchThread();
+    Hash hash = node.getCommitHash();
+    T details = myCache.get(hash);
+    if (details != null) {
+      return details;
+    }
+
+    T loadingDetails = (T)new LoadingDetails(hash);
+    runLoadAroundCommitData(node);
+    return loadingDetails;
+  }
+
+  @Nullable
+  private Node getCommitNodeInRow(int rowIndex) {
+    Graph graph = myDataHolder.getDataPack().getGraphModel().getGraph();
+    if (rowIndex < 0 || rowIndex >= graph.getNodeRows().size()) {
+      return null;
+    }
+    NodeRow row = graph.getNodeRows().get(rowIndex);
+    for (Node node : row.getNodes()) {
+      if (node.getType() == Node.NodeType.COMMIT_NODE) {
+        return node;
+      }
+    }
+    return null;
+  }
+
+  private void runLoadAroundCommitData(@NotNull Node node) {
+    int rowIndex = node.getRowIndex();
+    List<Node> nodes = new ArrayList<Node>();
+    for (int i = rowIndex - UP_PRELOAD_COUNT; i < rowIndex + DOWN_PRELOAD_COUNT; i++) {
+      Node commitNode = getCommitNodeInRow(i);
+      if (commitNode != null) {
+        nodes.add(commitNode);
+        Hash hash = commitNode.getCommitHash();
+
+        // fill the cache with temporary "Loading" values to avoid producing queries for each commit that has not been cached yet,
+        // even if it will be loaded within a previous query
+        if (!myCache.isKeyCached(hash)) {
+          myCache.put(hash, (T)new LoadingDetails(hash));
+        }
+      }
+    }
+    myLoader.addFirst(new TaskDescriptor(nodes));
+  }
+
+  private void preLoadCommitData(@NotNull List<Node> nodes) throws VcsException {
+    List<String> hashes = ContainerUtil.map(nodes, new Function<Node, String>() {
+      @Override
+      public String fun(Node node) {
+        return node.getCommitHash().toStrHash();
+      }
+    });
+
+    List<? extends T> details = readDetails(hashes);
+    saveInCache(details);
+  }
+
+  public void saveInCache(final List<? extends T> details) {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        for (T data : details) {
+          myCache.put(data.getHash(), data);
+        }
+      }
+    });
+  }
+
+  protected abstract List<? extends T> readDetails(List<String> hashes) throws VcsException;
+
+  /**
+   * This listener will be notified when any details loading process finishes.
+   * The notification will happen in the EDT.
+   */
+  public void addDetailsLoadedListener(@NotNull Runnable runnable) {
+    myLoadingFinishedListeners.add(runnable);
+  }
+
+  private static class TaskDescriptor {
+    private final List<Node> nodes;
+
+    private TaskDescriptor(List<Node> nodes) {
+      this.nodes = nodes;
+    }
+  }
+
+  private class DetailsLoadingTask implements Consumer<TaskDescriptor> {
+    private static final int MAX_LOADINGS = 10;
+
+    @Override
+    public void consume(final TaskDescriptor task) {
+      try {
+        myLoader.dismissLastTasks(MAX_LOADINGS);
+        preLoadCommitData(task.nodes);
+        UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+          @Override
+          public void run() {
+            for (Runnable loadingFinishedListener : myLoadingFinishedListeners) {
+              loadingFinishedListener.run();
+            }
+          }
+        });
+      }
+      catch (VcsException e) {
+        throw new RuntimeException(e); // todo
+      }
+    }
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/DataPack.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/DataPack.java
new file mode 100644 (file)
index 0000000..4249858
--- /dev/null
@@ -0,0 +1,232 @@
+package org.hanuna.gitalk.data;
+
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.util.Consumer;
+import com.intellij.util.Function;
+import com.intellij.util.containers.Predicate;
+import com.intellij.vcs.log.CommitParents;
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.Ref;
+import org.hanuna.gitalk.common.MyTimer;
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.hanuna.gitalk.data.rebase.FakeCommitParents;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.GraphBuilder;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.hanuna.gitalk.graphmodel.GraphModel;
+import org.hanuna.gitalk.graphmodel.impl.GraphModelImpl;
+import org.hanuna.gitalk.printmodel.GraphPrintCellModel;
+import org.hanuna.gitalk.printmodel.impl.GraphPrintCellModelImpl;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author erokhins
+ */
+public class DataPack {
+
+  @NotNull private final GraphModel myGraphModel;
+  @NotNull private final RefsModel myRefsModel;
+  @NotNull private final GraphPrintCellModel myPrintCellModel;
+
+  @NotNull
+  public static DataPack build(@NotNull List<? extends CommitParents> commits, @NotNull Collection<Ref> allRefs,
+                               @NotNull ProgressIndicator indicator) {
+    indicator.setText("Building graph...");
+
+    MutableGraph graph = GraphBuilder.build(commits, allRefs);
+
+    GraphModel graphModel = new GraphModelImpl(graph, allRefs);
+
+    final GraphPrintCellModel printCellModel = new GraphPrintCellModelImpl(graphModel.getGraph());
+    graphModel.addUpdateListener(new Consumer<UpdateRequest>() {
+      @Override
+      public void consume(UpdateRequest key) {
+        printCellModel.recalculate(key);
+      }
+    });
+
+    final RefsModel refsModel = new RefsModel(allRefs);
+    graphModel.getFragmentManager().setUnconcealedNodeFunction(new Function<Node, Boolean>() {
+      @NotNull
+      @Override
+      public Boolean fun(@NotNull Node key) {
+        if (key.getDownEdges().isEmpty() || key.getUpEdges().isEmpty() || refsModel.isBranchRef(key.getCommitHash())) {
+          return true;
+        }
+        else {
+          return false;
+        }
+      }
+    });
+    return new DataPack(graphModel, refsModel, printCellModel);
+  }
+
+  private DataPack(@NotNull GraphModel graphModel, @NotNull RefsModel refsModel, @NotNull GraphPrintCellModel printCellModel) {
+    myGraphModel = graphModel;
+    myRefsModel = refsModel;
+    myPrintCellModel = printCellModel;
+  }
+
+  public void appendCommits(@NotNull List<? extends CommitParents> commitParentsList) {
+    MyTimer timer = new MyTimer("append commits");
+    myGraphModel.appendCommitsToGraph(commitParentsList);
+    timer.print();
+  }
+
+  @NotNull
+  public RefsModel getRefsModel() {
+    return myRefsModel;
+  }
+
+  @NotNull
+  public GraphModel getGraphModel() {
+    return myGraphModel;
+  }
+
+  @NotNull
+  public GraphPrintCellModel getPrintCellModel() {
+    return myPrintCellModel;
+  }
+
+  public boolean isSameBranch(@NotNull Node nodeA, @NotNull Node nodeB) {
+    Node up, down;
+    if (nodeA.getRowIndex() > nodeB.getRowIndex()) {
+      up = nodeB;
+      down = nodeA;
+    } else {
+      up = nodeA;
+      down = nodeB;
+    }
+    return getGraphModel().getFragmentManager().getUpNodes(down).contains(up);
+  }
+
+  @NotNull
+  public Set<Node> getUpRefNodes(@NotNull GraphElement graphElement) {
+    Set<Node> nodes = new HashSet<Node>();
+    for (Node node : getGraphModel().getFragmentManager().getUpNodes(graphElement)) {
+      if (getRefsModel().isBranchRef(node.getCommitHash())) {
+        nodes.add(node);
+      }
+    }
+    return nodes;
+  }
+
+  @Nullable
+  public Node getNode(int rowIndex) {
+    return getGraphModel().getGraph().getCommitNodeInRow(rowIndex);
+  }
+
+  public int getRowByHash(Hash commitHash) {
+    Node node = getNodeByHash(commitHash);
+    return node == null ? -1 : node.getRowIndex();
+  }
+
+  @Nullable
+  public Node getNodeByHash(Hash hash) {
+    Graph graph = getGraphModel().getGraph();
+    for (int i = 0; i < graph.getNodeRows().size(); i++) {
+      Node node = graph.getCommitNodeInRow(i);
+      if (node != null && node.getCommitHash().equals(hash)) {
+        return node;
+      }
+    }
+    return null;
+  }
+
+  public Node getFakeNodeByHash(Hash hash, boolean original) {
+    hash = originalIfNeeded(hash, original);
+    Graph graph = getGraphModel().getGraph();
+    for (int i = 0; i < graph.getNodeRows().size(); i++) {
+      Node node = graph.getCommitNodeInRow(i);
+      if (node != null) {
+        Hash commitHash = node.getCommitHash();
+        if (FakeCommitParents.isFake(commitHash) && originalIfNeeded(commitHash, original).equals(hash)) {
+          return node;
+        }
+      }
+    }
+    return null;
+  }
+
+  private static Hash originalIfNeeded(Hash hash, boolean original) {
+    if (original) return FakeCommitParents.getOriginal(hash);
+    return hash;
+  }
+
+  @Nullable
+  public Node getCommonParent(Node a, Node b) {
+    List<Node> commitDiff = getCommitsDownToCommon(a, b);
+    return commitDiff.isEmpty() ? null : commitDiff.get(commitDiff.size() - 1);
+  }
+
+  public boolean isAncestorOf(Node ancestor, Node child) {
+    return ancestor != child && getGraphModel().getFragmentManager().getUpNodes(ancestor).contains(child);
+  }
+
+  @Nullable
+  public Node getCommonParent(Node a, Node b, Node c) {
+    return null;
+  }
+
+  public List<Node> getCommitsDownToCommon(Node newBase, Node head) {
+    final List<Node> all = getAllAncestors(newBase, new Predicate<Node>() {
+      @Override
+      public boolean apply(@Nullable Node input) {
+        return false;
+      }
+    });
+    return getAllAncestors(head, new Predicate<Node>() {
+      @Override
+      public boolean apply(@Nullable Node input) {
+        return all.contains(input);
+      }
+    });
+  }
+
+  private List<Node> getAllAncestors(Node a, Predicate<Node> stop) {
+    Set<Node> all = new LinkedHashSet<Node>();
+    Queue<Node> queue = new ArrayDeque<Node>();
+    queue.add(a);
+    while (!queue.isEmpty()) {
+      Node aNode = queue.remove();
+      all.add(aNode);
+      if (stop.apply(aNode)) {
+        return new ArrayList<Node>(all);
+      }
+      for (Edge edge : aNode.getDownEdges()) {
+        queue.add(edge.getDownNode());
+      }
+    }
+    return new ArrayList<Node>(all);
+  }
+
+  @Nullable
+  public Ref findRefOfNode(Node node) {
+    for (Ref ref : getRefsModel().getAllRefs()) {
+      if (ref.getCommitHash().equals(node.getCommitHash())) {
+        return ref;
+      }
+    }
+    return null;
+  }
+
+  public List<Node> getCommitsInBranchAboveBase(Node base, Node branchHead) {
+    List<Node> result = new ArrayList<Node>();
+    Node node = branchHead;
+    while (node != base) {
+      result.add(node);
+      // TODO: multiple edges must not appear
+      // TODO: if there are no edges, we are in the wrong branch
+      node = node.getDownEdges().get(0).getDownNode();
+    }
+    //Collections.reverse(result);
+    return result;
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/FakeCommitsInfo.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/FakeCommitsInfo.java
new file mode 100644 (file)
index 0000000..9c7e7d8
--- /dev/null
@@ -0,0 +1,24 @@
+package org.hanuna.gitalk.data;
+
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.data.rebase.FakeCommitParents;
+import com.intellij.vcs.log.Ref;
+
+import java.util.List;
+
+public class FakeCommitsInfo {
+
+  public final List<FakeCommitParents> commits;
+  public final Node base;
+  public final int insertAbove;
+  public final Ref resultRef;
+  public final Ref subjectRef;
+
+  public FakeCommitsInfo(List<FakeCommitParents> commits, Node base, int insertAbove, Ref resultRef, Ref subjectRef) {
+    this.commits = commits;
+    this.base = base;
+    this.insertAbove = insertAbove;
+    this.resultRef = resultRef;
+    this.subjectRef = subjectRef;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/LoadingDetails.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/LoadingDetails.java
new file mode 100644 (file)
index 0000000..89503e7
--- /dev/null
@@ -0,0 +1,90 @@
+package org.hanuna.gitalk.data;
+
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.vcs.log.CommitParents;
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.VcsCommitDetails;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Fake {@link VcsCommitDetails} implementation that is used to indicate that details are not ready for the moment,
+ * they are being retrieved from the VCS.
+ *
+ * @author Kirill Likhodedov
+ */
+public class LoadingDetails implements VcsCommitDetails, CommitParents {
+
+  @NotNull private final Hash myHash;
+
+  public LoadingDetails(@NotNull Hash hash) {
+    myHash = hash;
+  }
+
+  @NotNull
+  @Override
+  public Hash getHash() {
+    return myHash;
+  }
+
+  @NotNull
+  @Override
+  public List<Hash> getParents() {
+    return Collections.emptyList();
+  }
+
+  @NotNull
+  @Override
+  public String getSubject() {
+    return "Loading...";
+  }
+
+  @NotNull
+  @Override
+  public String getAuthorName() {
+    return "";
+  }
+
+  @Override
+  public long getAuthorTime() {
+    return -1;
+  }
+
+  @NotNull
+  @Override
+  public String getFullMessage() {
+    return "";
+  }
+
+  @NotNull
+  @Override
+  public String getAuthorEmail() {
+    return "";
+  }
+
+  @NotNull
+  @Override
+  public String getCommitterName() {
+    return "";
+  }
+
+  @NotNull
+  @Override
+  public String getCommitterEmail() {
+    return "";
+  }
+
+  @Override
+  public long getCommitTime() {
+    return -1;
+  }
+
+  @NotNull
+  @Override
+  public Collection<Change> getChanges() {
+    return Collections.emptyList();
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/MiniDetailsGetter.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/MiniDetailsGetter.java
new file mode 100644 (file)
index 0000000..e7b73b1
--- /dev/null
@@ -0,0 +1,24 @@
+package org.hanuna.gitalk.data;
+
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.vcs.log.VcsCommit;
+import com.intellij.vcs.log.VcsLogProvider;
+
+import java.util.List;
+
+/**
+ * @author Kirill Likhodedov
+ */
+public class MiniDetailsGetter extends DataGetter<VcsCommit> {
+
+  MiniDetailsGetter(VcsLogDataHolder dataHolder, VcsLogProvider logProvider, VirtualFile root) {
+    super(dataHolder, logProvider, root, new VcsCommitCache<VcsCommit>());
+  }
+
+  @Override
+  protected List<? extends VcsCommit> readDetails(List<String> hashes) throws VcsException {
+    return myLogProvider.readMiniDetails(myRoot, hashes);
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/RefsModel.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/RefsModel.java
new file mode 100644 (file)
index 0000000..181895d
--- /dev/null
@@ -0,0 +1,60 @@
+package org.hanuna.gitalk.data;
+
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.Ref;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * @author erokhins
+ */
+public class RefsModel {
+  private final Collection<Ref> allRefs;
+  private final Set<Hash> trackedCommitHashes = new HashSet<Hash>();
+
+  public RefsModel(Collection<Ref> allRefs) {
+    this.allRefs = allRefs;
+    computeTrackedCommitHash();
+  }
+
+  private void computeTrackedCommitHash() {
+    for (Ref ref : allRefs) {
+      trackedCommitHashes.add(ref.getCommitHash());
+    }
+  }
+
+  public boolean isBranchRef(@NotNull Hash commitHash) {
+    for (Ref ref : refsToCommit(commitHash)) {
+      if (ref.getType().isBranch()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @NotNull
+  public List<Ref> refsToCommit(@NotNull Hash hash) {
+    List<Ref> refs = new ArrayList<Ref>();
+    if (trackedCommitHashes.contains(hash)) {
+      for (Ref ref : allRefs) {
+        if (ref.getCommitHash().equals(hash)) {
+          refs.add(ref);
+        }
+      }
+    }
+    return refs;
+  }
+
+  @NotNull
+  public Set<Hash> getTrackedCommitHashes() {
+    return Collections.unmodifiableSet(trackedCommitHashes);
+  }
+
+  @NotNull
+  public Collection<Ref> getAllRefs() {
+    return Collections.unmodifiableCollection(allRefs);
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/VcsCommitCache.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/VcsCommitCache.java
new file mode 100644 (file)
index 0000000..b307c46
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2013 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 org.hanuna.gitalk.data;
+
+import com.intellij.util.containers.SLRUMap;
+import com.intellij.vcs.log.CommitParents;
+import com.intellij.vcs.log.Hash;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+
+/**
+ * <p>The cache of commit details.</p>
+ * <p>It is not actually a cache, but rather a limited map, because there is intentionally no way to get the non-cached value if it was not
+ *    found in the cache: such functionality is implemented by the {@link DataGetter} which is able to receive
+ *    non-cached details more efficiently, in a batch.</p>
+ * <p>Any access to the Cache MUST be performed from the EDT thread.</p>
+ *
+ * @author Kirill Likhodedov
+ */
+class VcsCommitCache<T extends CommitParents> {
+
+  private final SLRUMap<Hash, T> myCache = new SLRUMap<Hash, T>(5000, 5000);
+
+  public void put(@NotNull Hash hash, @NotNull T commit) {
+    assert EventQueue.isDispatchThread();
+    myCache.put(hash, commit);
+  }
+
+  public boolean isKeyCached(@NotNull Hash hash) {
+    assert EventQueue.isDispatchThread();
+    return myCache.get(hash) != null;
+  }
+
+  @Nullable
+  public T get(@NotNull Hash hash) {
+    assert EventQueue.isDispatchThread();
+    return myCache.get(hash);
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/VcsLogDataHolder.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/VcsLogDataHolder.java
new file mode 100644 (file)
index 0000000..ce80dfb
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2000-2013 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 org.hanuna.gitalk.data;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.BackgroundTaskQueue;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Consumer;
+import com.intellij.util.ThrowableConsumer;
+import com.intellij.util.messages.Topic;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.vcs.log.*;
+import org.hanuna.gitalk.common.compressedlist.VcsLogLogger;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * <p>Holds the commit data loaded from the VCS, and is capable to {@link #refresh(Runnable) refresh} this data by request.</p>
+ * <p>The commit data is acquired via {@link #getDataPack()}.</p>
+ * <p>If refresh is in progress, {@link #getDataPack()} returns the previous data pack (possible not actual anymore).
+ * When refresh completes, the data pack instance is updated. Refreshes are chained.</p>
+ * <p>Thread-safety: TODO </p>
+ * <p/>
+ * TODO: error handling
+ *
+ * @author Kirill Likhodedov
+ */
+public class VcsLogDataHolder implements VcsLogRefresher, Disposable {
+
+  public static final Topic<Runnable> REFRESH_COMPLETED = Topic.create("Vcs.Log.Completed", Runnable.class);
+
+  private static final Logger LOG = VcsLogLogger.LOG;
+
+  @NotNull private final Project myProject;
+  @NotNull private final VcsLogProvider myLogProvider;
+  @NotNull private final VirtualFile myRoot;
+  @NotNull private final BackgroundTaskQueue myDataLoaderQueue;
+  @NotNull private final MiniDetailsGetter myMiniDetailsGetter;
+  @NotNull private final CommitDetailsGetter myDetailsGetter;
+  @NotNull private final VcsLogJoiner myLogJoiner;
+
+  @NotNull private volatile DataPack myDataPack;
+  @Nullable private volatile List<CommitParents> myAllLog; // null means the whole log was not yet read from the VCS
+
+  public VcsLogDataHolder(@NotNull Project project, @NotNull VcsLogProvider logProvider, @NotNull VirtualFile root) {
+    myProject = project;
+    myLogProvider = logProvider;
+    myRoot = root;
+    myDataLoaderQueue = new BackgroundTaskQueue(project, "Loading history...");
+    myMiniDetailsGetter = new MiniDetailsGetter(this, logProvider, root);
+    myDetailsGetter = new CommitDetailsGetter(this, logProvider, root);
+    myLogJoiner = new VcsLogJoiner();
+  }
+
+  /**
+   * Initializes the VcsLogDataHolder in background in the following sequence:
+   * <ul>
+   * <li>Loads the first part of the log with details.</li>
+   * <li>Invokes the Consumer to initialize the UI with the initial data pack.</li>
+   * <li>Loads the whole log in background. When completed, substitutes the data and tells the UI to refresh itself.</li>
+   * </ul>
+   *
+   * @param onInitialized This is called when the holder is initialized with the initial data received from the VCS.
+   *                      The consumer is called on the EDT.
+   */
+  public static void init(@NotNull final Project project, @NotNull final VcsLogProvider logProvider, @NotNull final VirtualFile root,
+                          @NotNull final Consumer<VcsLogDataHolder> onInitialized) {
+    final VcsLogDataHolder dataHolder = new VcsLogDataHolder(project, logProvider, root);
+    dataHolder.initialize(onInitialized);
+  }
+
+  private void initialize(@NotNull final Consumer<VcsLogDataHolder> onInitialized) {
+    loadFirstPart(new Consumer<DataPack>() {
+      @Override
+      public void consume(DataPack dataPack) {
+        myDataPack = dataPack;
+        onInitialized.consume(VcsLogDataHolder.this);
+        loadAllLog();
+      }
+    }, true);
+  }
+
+  private void loadAllLog() {
+    runInBackground(new ThrowableConsumer<ProgressIndicator, VcsException>() {
+      @Override
+      public void consume(ProgressIndicator indicator) throws VcsException {
+        myAllLog = myLogProvider.readAllHashes(myRoot);
+      }
+    });
+  }
+
+  public void rebuildLog(@NotNull final Runnable onSuccess) {
+    runInBackground(new ThrowableConsumer<ProgressIndicator, VcsException>() {
+      @Override
+      public void consume(ProgressIndicator indicator) throws VcsException {
+        if (myAllLog != null) {
+          Collection<Ref> refs = myLogProvider.readAllRefs(myRoot);
+          myDataPack = DataPack.build(myAllLog, refs, indicator);
+          UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+            @Override
+            public void run() {
+              notifyAboutDataRefresh();
+              onSuccess.run();
+            }
+          });
+
+        }
+      }
+    });
+  }
+
+  /**
+   * @param onSuccess this task is called on the EDT after loading and graph building completes.
+   * @param ordered   passed to the {@link VcsLogProvider} to tell it is it is necessary to get commits from the VCS topologically ordered.
+   */
+  private void loadFirstPart(final Consumer<DataPack> onSuccess, final boolean ordered) {
+    runInBackground(new ThrowableConsumer<ProgressIndicator, VcsException>() {
+      @Override
+      public void consume(ProgressIndicator indicator) throws VcsException {
+        List<? extends VcsCommitDetails> firstBlock = myLogProvider.readFirstBlock(myRoot, ordered);
+        Collection<Ref> refs = myLogProvider.readAllRefs(myRoot);
+
+        myDetailsGetter.saveInCache(firstBlock);
+        myMiniDetailsGetter.saveInCache(firstBlock);
+
+        List<? extends CommitParents> refreshedLog;
+        if (myAllLog == null) {
+          // the whole log is not loaded before the first refresh
+          refreshedLog = firstBlock;
+        }
+        else {
+          assert myAllLog != null : "The whole log can't become null once loaded";
+          refreshedLog = myLogJoiner.addCommits(myAllLog, firstBlock, refs, new Computable<List<CommitParents>>() {
+            @Override
+            public List<CommitParents> compute() {
+              // TODO save the whole log in a file; keep only a part of it in memory; read from the file here
+              return readLogFromStorage();
+            }
+          });
+        }
+
+        myDataPack = DataPack.build(refreshedLog, refs, indicator);
+
+        UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+          @Override
+          public void run() {
+            onSuccess.consume(myDataPack);
+          }
+        });
+      }
+    });
+  }
+
+  @SuppressWarnings("ConstantConditions")
+  @NotNull
+  private List<CommitParents> readLogFromStorage() {
+    return myAllLog;
+  }
+
+  private void runInBackground(final ThrowableConsumer<ProgressIndicator, VcsException> task) {
+    myDataLoaderQueue.run(new Task.Backgroundable(myProject, "Loading history...") {
+      @Override
+      public void run(@NotNull ProgressIndicator indicator) {
+        try {
+          task.consume(indicator);
+        }
+        catch (VcsException e) {
+          throw new RuntimeException(e); // TODO
+        }
+      }
+    });
+  }
+
+  public void refresh(@NotNull final Runnable onSuccess) {
+    loadFirstPart(new Consumer<DataPack>() {
+      @Override
+      public void consume(DataPack dataPack) {
+        onSuccess.run();
+      }
+    }, false);
+  }
+
+  @Override
+  public void refreshCompletely() {
+    initialize(Consumer.EMPTY_CONSUMER);
+  }
+
+  @Override
+  public void refresh(@NotNull VirtualFile root) {
+    refresh(new Runnable() {
+      @Override
+      public void run() {
+        notifyAboutDataRefresh();
+      }
+    });
+  }
+
+  @Override
+  public void refreshRefs(@NotNull VirtualFile root) {
+    refresh(root); // TODO no need to query the VCS for commit & rebuild the whole log; just replace refs labels.
+  }
+
+  @NotNull
+  public DataPack getDataPack() {
+    return myDataPack;
+  }
+
+  private void notifyAboutDataRefresh() {
+    if (!myProject.isDisposed()) {
+      myProject.getMessageBus().syncPublisher(REFRESH_COMPLETED).run();
+    }
+  }
+
+  public CommitDetailsGetter getCommitDetailsGetter() {
+    return myDetailsGetter;
+  }
+
+  @NotNull
+  public MiniDetailsGetter getMiniDetailsGetter() {
+    return myMiniDetailsGetter;
+  }
+
+  @Override
+  public void dispose() {
+    myAllLog = null;
+    myDataLoaderQueue.clear();
+  }
+
+  public boolean isAllLogReady() {
+    return myAllLog != null;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/VcsLogJoiner.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/VcsLogJoiner.java
new file mode 100644 (file)
index 0000000..a0db188
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2000-2013 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 org.hanuna.gitalk.data;
+
+import com.intellij.openapi.util.Computable;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.HashSet;
+import com.intellij.vcs.log.CommitParents;
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.Ref;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * Attaches the block of latest commits, which was read from the VCS, to the existing log structure.
+ *
+ * @author Kirill Likhodedov
+ */
+public class VcsLogJoiner {
+
+  /**
+   *
+   * @param savedLog       currently available part of the log.
+   * @param firstBlock     the first n commits read from the VCS.
+   * @param refs           all references (branches) of the repository.
+   * @param wholeLogGetter the function which will read the whole log from the file, if the part stored in {@code savedLog} is not enough.
+   * @return New commits attached to the existing log structure.
+   */
+  @NotNull
+  public List<? extends CommitParents> addCommits(@NotNull List<CommitParents> savedLog,
+                                                  @NotNull List<? extends CommitParents> firstBlock, @NotNull Collection<Ref> refs,
+                                                  @NotNull Computable<List<CommitParents>> wholeLogGetter) {
+    int unsafeBlockSize = getFirstSafeIndex(savedLog, firstBlock, refs);
+    if (unsafeBlockSize == -1) {
+      savedLog = wholeLogGetter.compute();
+      unsafeBlockSize = getFirstSafeIndex(savedLog, firstBlock, refs);
+    }
+    if (unsafeBlockSize == -1) { // firstBlock not enough
+      //TODO
+      throw new IllegalStateException();
+    }
+
+    List<CommitParents> unsafePartSavedLog = new ArrayList<CommitParents>(savedLog.subList(0, unsafeBlockSize));
+    Set<CommitParents> allNewsCommits = getAllNewCommits(unsafePartSavedLog, firstBlock);
+    unsafePartSavedLog = new NewCommitIntegrator(unsafePartSavedLog, allNewsCommits).getResultList();
+
+    return ContainerUtil.concat(unsafePartSavedLog, savedLog.subList(unsafeBlockSize, savedLog.size()));
+  }
+
+  /**
+   *
+   * @param savedLog       currently available part of the log.
+   * @param firstBlock     the first n commits read from the VCS.
+   * @param refs           all references (branches) of the repository.
+   * @return -1 if not enough commits in firstBlock
+   */
+  private static int getFirstSafeIndex(@NotNull List<CommitParents> savedLog,
+                                       @NotNull List<? extends CommitParents> firstBlock,
+                                       @NotNull Collection<Ref> refs) {
+    Set<Hash> allUnresolvedLinkedHashes = new HashSet<Hash>();
+    for (Ref ref: refs) {
+      allUnresolvedLinkedHashes.add(ref.getCommitHash());
+    }
+    for (CommitParents commit : firstBlock) {
+      allUnresolvedLinkedHashes.addAll(commit.getParents());
+    }
+    for (CommitParents commit : firstBlock) {
+      allUnresolvedLinkedHashes.remove(commit.getHash());
+    }
+    return getFirstUnTrackedIndex(savedLog, allUnresolvedLinkedHashes);
+  }
+
+  private static int getFirstUnTrackedIndex(@NotNull List<CommitParents> commits, @NotNull Set<Hash> searchHashes) {
+    int lastIndex = 0;
+    for (CommitParents commit : commits) {
+      if (searchHashes.size() == 0) {
+        return lastIndex;
+      }
+      searchHashes.remove(commit.getHash());
+      lastIndex++;
+    }
+    return -1;
+  }
+
+  private static Set<CommitParents> getAllNewCommits(@NotNull List<CommitParents> unsafePartSavedLog,
+                                                     @NotNull List<? extends CommitParents> firstBlock) {
+    Set<Hash> existedCommitHashes = new HashSet<Hash>();
+    for (CommitParents commit : unsafePartSavedLog) {
+      existedCommitHashes.add(commit.getHash());
+    }
+    Set<CommitParents> allNewsCommits = new HashSet<CommitParents>();
+    for (CommitParents newCommit : firstBlock) {
+      if (!existedCommitHashes.contains(newCommit.getHash())) {
+        allNewsCommits.add(newCommit);
+      }
+    }
+    return allNewsCommits;
+  }
+
+
+  private static class NewCommitIntegrator {
+    private final List<CommitParents> list;
+    private final Map<Hash, CommitParents> newCommitsMap;
+
+    private NewCommitIntegrator(@NotNull List<CommitParents> list, @NotNull Set<CommitParents> newCommits) {
+      this.list = list;
+      newCommitsMap = new HashMap<Hash, CommitParents>();
+      for (CommitParents commit : newCommits) {
+        newCommitsMap.put(commit.getHash(), commit);
+      }
+    }
+
+    // return insert Index
+    private int insertToList(@NotNull CommitParents commit) {
+      if (!newCommitsMap.containsKey(commit.getHash())) {
+        throw new IllegalStateException("Commit was inserted, but insert call again. Commit hash: " + commit.getHash());
+      }
+      //insert all parents commits
+      for (Hash parentHash : commit.getParents()) {
+        CommitParents parentCommit = newCommitsMap.get(parentHash);
+        if (parentCommit != null) {
+          insertToList(parentCommit);
+        }
+      }
+
+      int insertIndex = getInsertIndex(commit.getParents());
+      list.add(insertIndex, commit);
+      newCommitsMap.remove(commit.getHash());
+      return insertIndex;
+    }
+
+    private int getInsertIndex(@NotNull Collection<Hash> parentHashes) {
+      if (parentHashes.size() == 0) {
+        return 0;
+      }
+      for (int i = 0; i < list.size(); i++) {
+        if (parentHashes.contains(list.get(i).getHash())) {
+          return i;
+        }
+      }
+      throw new IllegalStateException("Not found parent Hash in list.");
+    }
+
+    private void insertAllCommits() {
+      Iterator<CommitParents> iterator = newCommitsMap.values().iterator();
+      while (iterator.hasNext()) {
+        insertToList(iterator.next());
+        iterator = newCommitsMap.values().iterator();
+      }
+    }
+
+    private List<CommitParents> getResultList() {
+      insertAllCommits();
+      return list;
+    }
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/rebase/FakeCommitParents.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/rebase/FakeCommitParents.java
new file mode 100644 (file)
index 0000000..9d8cb22
--- /dev/null
@@ -0,0 +1,70 @@
+package org.hanuna.gitalk.data.rebase;
+
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.CommitParents;
+import com.intellij.vcs.log.RebaseCommand;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+public class FakeCommitParents implements CommitParents {
+
+  private static final String FAKE_HASH_PREFIX = "aaaaaaaaaaaaa00000000";
+
+  public static boolean isFake(Hash hash) {
+    return isFake(hash.toStrHash());
+  }
+
+  public static boolean isFake(String hashStr) {
+    return hashStr.startsWith(FAKE_HASH_PREFIX);
+  }
+
+  public static Hash getOriginal(Hash hash) {
+    return Hash.build(getOriginal(hash.toStrHash()));
+  }
+
+  public static String getOriginal(String hash) {
+    while (isFake(hash)) {
+      hash = hash.substring(FAKE_HASH_PREFIX.length());
+    }
+    return hash;
+  }
+
+  public static Hash fakeHash(Hash hash) {
+    return Hash.build(FAKE_HASH_PREFIX + getOriginal(hash.toStrHash()));
+  }
+
+  private final RebaseCommand command;
+  private final Hash fakeHash;
+
+  private final Hash parent;
+
+  public FakeCommitParents(@NotNull Hash parent, @NotNull RebaseCommand command) {
+    this.parent = parent;
+    this.command = command;
+    this.fakeHash = fakeHash(command.getCommit());
+  }
+
+  @NotNull
+  @Override
+  public Hash getHash() {
+    return fakeHash;
+  }
+
+  @NotNull
+  @Override
+  public List<Hash> getParents() {
+    return Collections.singletonList(parent);
+  }
+
+  @NotNull
+  public RebaseCommand getCommand() {
+    return command;
+  }
+
+  @Override
+  public String toString() {
+    return fakeHash + " -> " + command;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/rebase/InteractiveRebaseBuilder.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/rebase/InteractiveRebaseBuilder.java
new file mode 100644 (file)
index 0000000..85c7d19
--- /dev/null
@@ -0,0 +1,41 @@
+package org.hanuna.gitalk.data.rebase;
+
+import org.hanuna.gitalk.graph.elements.Node;
+import com.intellij.vcs.log.RebaseCommand;
+import com.intellij.vcs.log.Ref;
+
+import java.util.Collections;
+import java.util.List;
+
+public class InteractiveRebaseBuilder {
+  public static final InteractiveRebaseBuilder EMPTY = new InteractiveRebaseBuilder();
+
+  public void startRebase(Ref subjectRef, Node onto) {
+
+  }
+
+  public void startRebaseOnto(Ref subjectRef, Node base, List<Node> nodesToRebase) {
+
+  }
+
+  public void moveCommits(Ref subjectRef, Node base, InsertPosition position, List<Node> nodesToInsert) {
+
+  }
+
+  public void fixUp(Ref subjectRef, Node target, List<Node> nodesToFixUp) {
+
+  }
+
+  public void reword(Ref subjectRef, Node commitToReword, String newMessage) {
+
+  }
+
+  public List<RebaseCommand> getRebaseCommands() {
+    return Collections.emptyList();
+  }
+
+  public enum InsertPosition {
+    ABOVE,
+    BELOW
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/rebase/VcsLogActionHandler.java b/platform/dvcs/vcs-log/vcs-log-data/src/org/hanuna/gitalk/data/rebase/VcsLogActionHandler.java
new file mode 100644 (file)
index 0000000..aeaedf5
--- /dev/null
@@ -0,0 +1,53 @@
+package org.hanuna.gitalk.data.rebase;
+
+import org.hanuna.gitalk.graph.elements.Node;
+import com.intellij.vcs.log.RebaseCommand;
+import com.intellij.vcs.log.Ref;
+
+import java.util.List;
+
+public interface VcsLogActionHandler {
+  VcsLogActionHandler DO_NOTHING = new VcsLogActionHandler() {
+    @Override
+    public void abortRebase() {
+    }
+
+    @Override
+    public void continueRebase() {
+    }
+
+    @Override
+    public void cherryPick(Ref targetRef, List<Node> nodesToPick, Callback callback) {
+    }
+
+    @Override
+    public void rebase(Node onto, Ref subjectRef, Callback callback) {
+    }
+
+    @Override
+    public void rebaseOnto(Node onto, Ref subjectRef, List<Node> nodesToRebase, Callback callback) {
+    }
+
+    @Override
+    public void interactiveRebase(Ref subjectRef, Node onto, Callback callback, List<RebaseCommand> commands) {
+    }
+  };
+
+  void abortRebase();
+
+  void continueRebase();
+
+  interface Callback {
+    void disableModifications();
+    void enableModifications();
+
+    void interactiveCommandApplied(RebaseCommand command);
+  }
+
+  void cherryPick(Ref targetRef, List<Node> nodesToPick, Callback callback);
+
+  void rebase(Node onto, Ref subjectRef, Callback callback);
+  void rebaseOnto(Node onto, Ref subjectRef, List<Node> nodesToRebase, Callback callback);
+
+  void interactiveRebase(Ref subjectRef, Node onto, Callback callback, List<RebaseCommand> commands);
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-data/vcs-log-data.iml b/platform/dvcs/vcs-log/vcs-log-data/vcs-log-data.iml
new file mode 100644 (file)
index 0000000..4a94e8f
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="vcs-log-common" />
+    <orderEntry type="module" module-name="vcs-log-print-model" />
+    <orderEntry type="module" module-name="core-api" />
+    <orderEntry type="module" module-name="vcs-log-api" />
+    <orderEntry type="module" module-name="vcs-api" />
+    <orderEntry type="module" module-name="vcs-log-graph" />
+    <orderEntry type="module" module-name="platform-impl" />
+  </component>
+</module>
+
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/Graph.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/Graph.java
new file mode 100644 (file)
index 0000000..4bd7eb7
--- /dev/null
@@ -0,0 +1,20 @@
+package org.hanuna.gitalk.graph;
+
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public interface Graph {
+
+  @NotNull
+  List<NodeRow> getNodeRows();
+
+  @Nullable
+  Node getCommitNodeInRow(int rowIndex);
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/Branch.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/Branch.java
new file mode 100644 (file)
index 0000000..a2fef8b
--- /dev/null
@@ -0,0 +1,78 @@
+package org.hanuna.gitalk.graph.elements;
+
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.Ref;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * @author erokhins
+ */
+public final class Branch {
+  private final Hash upCommitHash;
+  private final Hash downCommitHash;
+  @Nullable private final Ref myRef;
+
+  public Branch(@NotNull Hash upCommitHash, @NotNull Hash downCommitHash, Collection<Ref> refs) {
+    this.upCommitHash = upCommitHash;
+    this.downCommitHash = downCommitHash;
+    myRef = findUpRef(upCommitHash, refs);
+  }
+
+  public Branch(Hash commit, Collection<Ref> refs) {
+    this(commit, commit, refs);
+  }
+
+  @Nullable
+  private static Ref findUpRef(Hash upCommitHash, Collection<Ref> refs) {
+    for (Ref ref : refs) {
+      if (ref.getType() != Ref.RefType.TAG && ref.getCommitHash().equals(upCommitHash)) {
+        return ref;
+      }
+    }
+    return null;
+  }
+
+  @NotNull
+  public Hash getUpCommitHash() {
+    return upCommitHash;
+  }
+
+  @NotNull
+  public Hash getDownCommitHash() {
+    return downCommitHash;
+  }
+
+  public int getBranchNumber() {
+    if (myRef == null) {
+      return upCommitHash.hashCode() + 73 * downCommitHash.hashCode();
+    }
+    return myRef.getName().hashCode();
+  }
+
+  @Override
+  public int hashCode() {
+    return upCommitHash.hashCode() + 73 * downCommitHash.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj != null && obj.getClass() == Branch.class) {
+      Branch anBranch = (Branch)obj;
+      return anBranch.upCommitHash == upCommitHash && anBranch.downCommitHash == downCommitHash;
+    }
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    if (upCommitHash == downCommitHash) {
+      return upCommitHash.toStrHash();
+    }
+    else {
+      return upCommitHash.toStrHash() + '#' + downCommitHash.toStrHash();
+    }
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/Edge.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/Edge.java
new file mode 100644 (file)
index 0000000..c5f7e4d
--- /dev/null
@@ -0,0 +1,23 @@
+package org.hanuna.gitalk.graph.elements;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public interface Edge extends GraphElement {
+
+  @NotNull
+  public Node getUpNode();
+
+  @NotNull
+  public Node getDownNode();
+
+  @NotNull
+  public EdgeType getType();
+
+  public static enum EdgeType {
+    USUAL,
+    HIDE_FRAGMENT
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/GraphElement.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/GraphElement.java
new file mode 100644 (file)
index 0000000..2b06143
--- /dev/null
@@ -0,0 +1,26 @@
+package org.hanuna.gitalk.graph.elements;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author erokhins
+ */
+public interface GraphElement {
+
+  @NotNull
+  public Branch getBranch();
+
+  /**
+   * @return node, if this GraphElement was Node, another - null
+   */
+  @Nullable
+  public Node getNode();
+
+  /**
+   * @return edge, if this GraphElement was Edge, another - null
+   */
+  @Nullable
+  public Edge getEdge();
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/Node.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/Node.java
new file mode 100644 (file)
index 0000000..9935fbc
--- /dev/null
@@ -0,0 +1,38 @@
+package org.hanuna.gitalk.graph.elements;
+
+import com.intellij.vcs.log.Hash;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public interface Node extends GraphElement {
+
+  int getRowIndex();
+
+  @NotNull
+  NodeType getType();
+
+  @NotNull
+  List<Edge> getUpEdges();
+
+  @NotNull
+  List<Edge> getDownEdges();
+
+  /**
+   * @return if type == COMMIT_NODE - this commit.
+   *         if type == EDGE_NODE - common Parent
+   *         if type == END_COMMIT_NODE - parent of This Commit
+   */
+  @NotNull
+  Hash getCommitHash();
+
+  enum NodeType {
+    COMMIT_NODE,
+    EDGE_NODE,
+    END_COMMIT_NODE
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/NodeRow.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/elements/NodeRow.java
new file mode 100644 (file)
index 0000000..93b0478
--- /dev/null
@@ -0,0 +1,16 @@
+package org.hanuna.gitalk.graph.elements;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public interface NodeRow {
+
+  @NotNull
+  List<Node> getNodes();
+
+  int getRowIndex();
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/GraphAppendBuilder.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/GraphAppendBuilder.java
new file mode 100644 (file)
index 0000000..52bf558
--- /dev/null
@@ -0,0 +1,121 @@
+package org.hanuna.gitalk.graph.mutable;
+
+import com.intellij.vcs.log.CommitParents;
+import com.intellij.vcs.log.Hash;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNode;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNodeRow;
+import com.intellij.vcs.log.Ref;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+import static org.hanuna.gitalk.graph.elements.Node.NodeType.*;
+
+/**
+ * @author erokhins
+ */
+//local package
+class GraphAppendBuilder {
+
+
+  private final MutableGraph graph;
+  private final Collection<Ref> myRefs;
+
+  public GraphAppendBuilder(MutableGraph graph, Collection<Ref> allRefs) {
+    this.graph = graph;
+    myRefs = allRefs;
+  }
+
+  private MutableNodeRow getLastRowInGraph() {
+    List<MutableNodeRow> allRows = graph.getAllRows();
+    assert !allRows.isEmpty() : "graph is empty!";
+    return allRows.get(allRows.size() - 1);
+  }
+
+  private boolean isSimpleEndOfGraph() {
+    List<MutableNodeRow> allRows = graph.getAllRows();
+    assert !allRows.isEmpty() : "graph is empty!";
+    MutableNodeRow lastRow = getLastRowInGraph();
+
+    boolean hasCommitNode = false;
+    for (MutableNode node : lastRow.getInnerNodeList()) {
+      if (node.getType() == COMMIT_NODE) {
+        hasCommitNode = true;
+      }
+    }
+    if (hasCommitNode) {
+      if (lastRow.getInnerNodeList().size() == 1) {
+        return true;
+      }
+      else {
+        throw new IllegalStateException("graph with commit node and more that 1 node in last row");
+      }
+    }
+    else {
+      return false;
+    }
+  }
+
+  private Map<Hash, MutableNode> fixUnderdoneNodes(@NotNull Hash firstHash) {
+    Map<Hash, MutableNode> underdoneNodes = new HashMap<Hash, MutableNode>();
+    List<MutableNode> nodesInLaseRow = getLastRowInGraph().getInnerNodeList();
+    MutableNode node;
+    for (Iterator<MutableNode> iterator = nodesInLaseRow.iterator(); iterator.hasNext(); ) {
+      node = iterator.next();
+
+      if (node.getType() != END_COMMIT_NODE) {
+        throw new IllegalStateException("bad last row in graph, unexpected node type: " + node.getType());
+      }
+      // i.e. it is EDGE_NODE
+      if (node.getInnerUpEdges().size() > 1) {
+        if (node.getCommitHash().equals(firstHash)) {
+          iterator.remove();
+          underdoneNodes.put(firstHash, node);
+        }
+        else {
+          node.setType(EDGE_NODE);
+          MutableNode newParentNode = new MutableNode(node.getBranch(), node.getCommitHash());
+          GraphBuilder.createUsualEdge(node, newParentNode, node.getBranch());
+          underdoneNodes.put(node.getCommitHash(), newParentNode);
+        }
+      }
+      else {
+        iterator.remove();
+        underdoneNodes.put(node.getCommitHash(), node);
+      }
+    }
+
+    return underdoneNodes;
+  }
+
+  private void simpleAppend(@NotNull List<? extends CommitParents> commitParentses,
+                            @NotNull MutableNodeRow nextRow,
+                            @NotNull Map<Hash, MutableNode> underdoneNodes) {
+    int startIndex = nextRow.getRowIndex();
+
+    Map<Hash, Integer> commitLogIndexes = new HashMap<Hash, Integer>(commitParentses.size());
+    for (int i = 0; i < commitParentses.size(); i++) {
+      commitLogIndexes.put(commitParentses.get(i).getHash(), i + startIndex);
+    }
+
+    GraphBuilder builder = new GraphBuilder(commitParentses.size() + startIndex - 1, commitLogIndexes, graph, underdoneNodes, nextRow,
+                                            myRefs);
+    builder.runBuild(commitParentses);
+  }
+
+  public void appendToGraph(@NotNull List<? extends CommitParents> commitParentses) {
+    if (commitParentses.size() == 0) {
+      throw new IllegalArgumentException("Empty list commitParentses");
+    }
+    if (isSimpleEndOfGraph()) {
+      int startIndex = getLastRowInGraph().getRowIndex() + 1;
+      simpleAppend(commitParentses, new MutableNodeRow(graph, startIndex), new HashMap<Hash, MutableNode>());
+    }
+    else {
+      Map<Hash, MutableNode> underdoneNodes = fixUnderdoneNodes(commitParentses.get(0).getHash());
+      MutableNodeRow lastRow = getLastRowInGraph();
+      graph.getAllRows().remove(graph.getAllRows().size() - 1);
+      simpleAppend(commitParentses, lastRow, underdoneNodes);
+    }
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/GraphBuilder.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/GraphBuilder.java
new file mode 100644 (file)
index 0000000..c3e19d5
--- /dev/null
@@ -0,0 +1,171 @@
+package org.hanuna.gitalk.graph.mutable;
+
+import com.intellij.vcs.log.CommitParents;
+import com.intellij.vcs.log.Hash;
+import org.hanuna.gitalk.graph.elements.Branch;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNode;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNodeRow;
+import org.hanuna.gitalk.graph.mutable.elements.UsualEdge;
+import com.intellij.vcs.log.Ref;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+import static org.hanuna.gitalk.graph.elements.Node.NodeType.*;
+
+/**
+ * @author erokhins
+ */
+public class GraphBuilder {
+
+  public static MutableGraph build(@NotNull List<? extends CommitParents> commitParentses, Collection<Ref> allRefs) {
+    Map<Hash, Integer> commitLogIndexes = new HashMap<Hash, Integer>(commitParentses.size());
+    for (int i = 0; i < commitParentses.size(); i++) {
+      commitLogIndexes.put(commitParentses.get(i).getHash(), i);
+    }
+    GraphBuilder builder = new GraphBuilder(commitParentses.size() - 1, commitLogIndexes, allRefs);
+    return builder.runBuild(commitParentses);
+  }
+
+  public static void addCommitsToGraph(@NotNull MutableGraph graph, @NotNull List<? extends CommitParents> commitParentses,
+                                       @NotNull Collection<Ref> allRefs) {
+    new GraphAppendBuilder(graph, allRefs).appendToGraph(commitParentses);
+  }
+
+  // local package
+  static void createUsualEdge(@NotNull MutableNode up, @NotNull MutableNode down, @NotNull Branch branch) {
+    UsualEdge edge = new UsualEdge(up, down, branch);
+    up.getInnerDownEdges().add(edge);
+    down.getInnerUpEdges().add(edge);
+  }
+
+  private final int lastLogIndex;
+  private final MutableGraph graph;
+  private final Map<Hash, MutableNode> underdoneNodes;
+  private Map<Hash, Integer> commitHashLogIndexes;
+  private Collection<Ref> myRefs;
+
+  private MutableNodeRow nextRow;
+
+  public GraphBuilder(int lastLogIndex,
+                      Map<Hash, Integer> commitHashLogIndexes,
+                      MutableGraph graph,
+                      Map<Hash, MutableNode> underdoneNodes,
+                      MutableNodeRow nextRow, Collection<Ref> refs) {
+    this.lastLogIndex = lastLogIndex;
+    this.commitHashLogIndexes = commitHashLogIndexes;
+    this.graph = graph;
+    this.underdoneNodes = underdoneNodes;
+    this.nextRow = nextRow;
+    myRefs = refs;
+  }
+
+  public GraphBuilder(int lastLogIndex, Map<Hash, Integer> commitHashLogIndexes, MutableGraph graph, Collection<Ref> refs) {
+    this(lastLogIndex, commitHashLogIndexes, graph, new HashMap<Hash, MutableNode>(), new MutableNodeRow(graph, 0), refs);
+  }
+
+  public GraphBuilder(int lastLogIndex, Map<Hash, Integer> commitHashLogIndexes, Collection<Ref> refs) {
+    this(lastLogIndex, commitHashLogIndexes, new MutableGraph(), refs);
+  }
+
+
+  private int getLogIndexOfCommit(@NotNull Hash commitHash) {
+    Integer index = commitHashLogIndexes.get(commitHash);
+    if (index == null) {
+      return lastLogIndex + 1;
+    }
+    else {
+      return index;
+    }
+  }
+
+
+  private MutableNode addCurrentCommitAndFinishRow(@NotNull Hash commitHash) {
+    MutableNode node = underdoneNodes.remove(commitHash);
+    if (node == null) {
+      node = createNode(commitHash, new Branch(commitHash, myRefs));
+    }
+    node.setType(COMMIT_NODE);
+    node.setNodeRow(nextRow);
+
+    nextRow.getInnerNodeList().add(node);
+    graph.getAllRows().add(nextRow);
+    nextRow = new MutableNodeRow(graph, nextRow.getRowIndex() + 1);
+    return node;
+  }
+
+  private void addParent(MutableNode node, Hash parentHash, Branch branch) {
+    MutableNode parentNode = underdoneNodes.remove(parentHash);
+    if (parentNode == null) {
+      parentNode = createNode(parentHash, branch);
+      createUsualEdge(node, parentNode, branch);
+      underdoneNodes.put(parentHash, parentNode);
+    }
+    else {
+      createUsualEdge(node, parentNode, branch);
+      int parentRowIndex = getLogIndexOfCommit(parentHash);
+
+      // i.e. we need of create EDGE_NODE node
+      if (nextRow.getRowIndex() != parentRowIndex) {
+        parentNode.setNodeRow(nextRow);
+        parentNode.setType(EDGE_NODE);
+        nextRow.getInnerNodeList().add(parentNode);
+
+        MutableNode newParentNode = createNode(parentHash, parentNode.getBranch());
+        createUsualEdge(parentNode, newParentNode, parentNode.getBranch());
+        underdoneNodes.put(parentHash, newParentNode);
+      }
+      else {
+        // i.e. node must be added in nextRow, when addCurrentCommitAndFinishRow() will called in next time
+        underdoneNodes.put(parentHash, parentNode);
+      }
+    }
+  }
+
+  private MutableNode createNode(Hash hash, Branch branch) {
+    return new MutableNode(branch, hash);
+  }
+
+  private void append(@NotNull CommitParents commitParents) {
+    MutableNode node = addCurrentCommitAndFinishRow(commitParents.getHash());
+
+    List<Hash> parents = commitParents.getParents();
+    if (parents.size() == 1) {
+      addParent(node, parents.get(0), node.getBranch());
+    }
+    else {
+      for (Hash parentHash : parents) {
+        addParent(node, parentHash, new Branch(node.getCommitHash(), parentHash, myRefs));
+      }
+    }
+  }
+
+
+  private void lastActions() {
+    Set<Hash> notReadiedCommitHashes = underdoneNodes.keySet();
+    for (Hash hash : notReadiedCommitHashes) {
+      MutableNode underdoneNode = underdoneNodes.get(hash);
+      underdoneNode.setNodeRow(nextRow);
+      underdoneNode.setType(END_COMMIT_NODE);
+      nextRow.getInnerNodeList().add(underdoneNode);
+    }
+    if (!nextRow.getInnerNodeList().isEmpty()) {
+      graph.getAllRows().add(nextRow);
+    }
+  }
+
+  // local package
+  @NotNull
+  MutableGraph runBuild(@NotNull List<? extends CommitParents> commitParentses) {
+    if (commitParentses.size() == 0) {
+      throw new IllegalArgumentException("Empty list commitParentses");
+    }
+    for (CommitParents commitParents : commitParentses) {
+      append(commitParents);
+    }
+    lastActions();
+    graph.updateVisibleRows();
+    return graph;
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/GraphDecorator.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/GraphDecorator.java
new file mode 100644 (file)
index 0000000..595d893
--- /dev/null
@@ -0,0 +1,21 @@
+package org.hanuna.gitalk.graph.mutable;
+
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public interface GraphDecorator {
+
+  public boolean isVisibleNode(@NotNull Node node);
+
+  @NotNull
+  public List<Edge> getDownEdges(@NotNull Node node, @NotNull List<Edge> innerDownEdges);
+
+  @NotNull
+  public List<Edge> getUpEdges(@NotNull Node node, @NotNull List<Edge> innerUpEdges);
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/MutableGraph.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/MutableGraph.java
new file mode 100644 (file)
index 0000000..f8d02b5
--- /dev/null
@@ -0,0 +1,84 @@
+package org.hanuna.gitalk.graph.mutable;
+
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNodeRow;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class MutableGraph implements Graph {
+  public static final GraphDecorator ID_DECORATOR = new GraphDecorator() {
+    @Override
+    public boolean isVisibleNode(@NotNull Node node) {
+      return true;
+    }
+
+    @NotNull
+    @Override
+    public List<Edge> getDownEdges(@NotNull Node node, @NotNull List<Edge> innerDownEdges) {
+      return innerDownEdges;
+    }
+
+    @NotNull
+    @Override
+    public List<Edge> getUpEdges(@NotNull Node node, @NotNull List<Edge> innerUpEdges) {
+      return innerUpEdges;
+    }
+
+  };
+
+  private final List<MutableNodeRow> allRows = new ArrayList<MutableNodeRow>();
+  private final List<MutableNodeRow> visibleRows = new ArrayList<MutableNodeRow>();
+  private GraphDecorator graphDecorator = ID_DECORATOR;
+
+  public GraphDecorator getGraphDecorator() {
+    return graphDecorator;
+  }
+
+  public void setGraphDecorator(GraphDecorator graphDecorator) {
+    this.graphDecorator = graphDecorator;
+  }
+
+  @Override
+  @NotNull
+  public List<NodeRow> getNodeRows() {
+    return Collections.<NodeRow>unmodifiableList(visibleRows);
+  }
+
+  @Nullable
+  @Override
+  public Node getCommitNodeInRow(int rowIndex) {
+    NodeRow nodeRow = visibleRows.get(rowIndex);
+    for (Node node : nodeRow.getNodes()) {
+      if (node.getType() == Node.NodeType.COMMIT_NODE) {
+        return node;
+      }
+    }
+    return null;
+  }
+
+  public List<MutableNodeRow> getAllRows() {
+    return allRows;
+  }
+
+  public void updateVisibleRows() {
+    visibleRows.clear();
+    for (MutableNodeRow row : allRows) {
+      if (!row.getNodes().isEmpty()) {
+        row.setRowIndex(visibleRows.size());
+        visibleRows.add(row);
+      }
+    }
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/FakeNode.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/FakeNode.java
new file mode 100644 (file)
index 0000000..4fb9adb
--- /dev/null
@@ -0,0 +1,20 @@
+package org.hanuna.gitalk.graph.mutable.elements;
+
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.RebaseCommand;
+import org.hanuna.gitalk.graph.elements.Branch;
+import org.jetbrains.annotations.NotNull;
+
+public class FakeNode extends MutableNode {
+  private final RebaseCommand myCommand;
+
+  public FakeNode(Branch branch, Hash hash, RebaseCommand command) {
+    super(branch, hash);
+    myCommand = command;
+  }
+
+  @NotNull
+  public RebaseCommand getCommand() {
+    return myCommand;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/MutableNode.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/MutableNode.java
new file mode 100644 (file)
index 0000000..32cd9de
--- /dev/null
@@ -0,0 +1,129 @@
+package org.hanuna.gitalk.graph.mutable.elements;
+
+import com.intellij.util.SmartList;
+import com.intellij.vcs.log.Hash;
+import org.hanuna.gitalk.graph.elements.Branch;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class MutableNode implements Node {
+  private final Branch branch;
+  private final Hash hash;
+  private MutableNodeRow nodeRow = null;
+  private NodeType type;
+
+  private final List<Edge> upEdges = new SmartList<Edge>();
+  private final List<Edge> downEdges = new SmartList<Edge>();
+
+  public MutableNode(Branch branch, Hash hash) {
+    this.branch = branch;
+    this.hash = hash;
+  }
+
+  @NotNull
+  public List<Edge> getInnerUpEdges() {
+    return upEdges;
+  }
+
+  @NotNull
+  public List<Edge> getInnerDownEdges() {
+    return downEdges;
+  }
+
+  @Override
+  public int getRowIndex() {
+    return nodeRow.getRowIndex();
+  }
+
+  public void setNodeRow(MutableNodeRow nodeRow) {
+    this.nodeRow = nodeRow;
+  }
+
+  public void setType(NodeType type) {
+    this.type = type;
+  }
+
+  @NotNull
+  @Override
+  public NodeType getType() {
+    return type;
+  }
+
+
+  @NotNull
+  @Override
+  public List<Edge> getUpEdges() {
+    return nodeRow.getGraphDecorator().getUpEdges(this, upEdges);
+        /*
+        GraphDecorator decorator = nodeRow.getGraphDecorator();
+        Edge edge = decorator.getHideFragmentUpEdge(this);
+        if (edge != null) {
+            return OneElementList.buildList(edge);
+        }
+
+        List<Edge> visibleEdge = new ArrayList<Edge>();
+        for (Edge upEdge : upEdges) {
+            if (decorator.isVisibleNode((MutableNode) upEdge.getUpNode())) {
+                visibleEdge.add(upEdge);
+            }
+        }
+        return visibleEdge;
+        */
+  }
+
+  @NotNull
+  @Override
+  public List<Edge> getDownEdges() {
+    return nodeRow.getGraphDecorator().getDownEdges(this, downEdges);
+        /*
+        GraphDecorator decorator = nodeRow.getGraphDecorator();
+        Edge edge = decorator.getHideFragmentDownEdge(this);
+        if (edge != null) {
+            return OneElementList.buildList(edge);
+        }
+        /*
+        slow method:
+        List<Edge> visibleEdge = new ArrayList<Edge>();
+        for (Edge downEdge : downEdges) {
+            if (decorator.isVisibleNode(downEdge.getDownNode())) {
+                visibleEdge.add(downEdge);
+            }
+        }
+        return visibleEdge;
+        return Collections.unmodifiableList(downEdges);
+        */
+  }
+
+  @NotNull
+  @Override
+  public Hash getCommitHash() {
+    return hash;
+  }
+
+  @NotNull
+  @Override
+  public Branch getBranch() {
+    return branch;
+  }
+
+  @Override
+  public Node getNode() {
+    return this;
+  }
+
+  @Override
+  public Edge getEdge() {
+    return null;
+  }
+
+  @Override
+  public String toString() {
+    return getType() + " " + getCommitHash();
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/MutableNodeRow.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/MutableNodeRow.java
new file mode 100644 (file)
index 0000000..00c27fb
--- /dev/null
@@ -0,0 +1,61 @@
+package org.hanuna.gitalk.graph.mutable.elements;
+
+import com.intellij.util.SmartList;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+import org.hanuna.gitalk.graph.mutable.GraphDecorator;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class MutableNodeRow implements NodeRow {
+  private final List<MutableNode> nodes = new SmartList<MutableNode>();
+  private final MutableGraph graph;
+  private int rowIndex;
+
+  public MutableNodeRow(@NotNull MutableGraph graph, int rowIndex) {
+    this.graph = graph;
+    this.rowIndex = rowIndex;
+  }
+
+  public void setRowIndex(int rowIndex) {
+    this.rowIndex = rowIndex;
+  }
+
+  @NotNull
+  public GraphDecorator getGraphDecorator() {
+    return graph.getGraphDecorator();
+  }
+
+  @NotNull
+  public List<MutableNode> getInnerNodeList() {
+    return nodes;
+  }
+
+  @NotNull
+  @Override
+  public List<Node> getNodes() {
+    List<Node> visibleNodes = new ArrayList<Node>(nodes.size());
+    for (Node node : nodes) {
+      if (getGraphDecorator().isVisibleNode(node)) {
+        visibleNodes.add(node);
+      }
+    }
+    return visibleNodes;
+  }
+
+  @Override
+  public int getRowIndex() {
+    return rowIndex;
+  }
+
+  @Override
+  public String toString() {
+    return getNodes().toString();
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/UsualEdge.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graph/mutable/elements/UsualEdge.java
new file mode 100644 (file)
index 0000000..38037fc
--- /dev/null
@@ -0,0 +1,55 @@
+package org.hanuna.gitalk.graph.mutable.elements;
+
+import org.hanuna.gitalk.graph.elements.Branch;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public class UsualEdge implements Edge {
+  private final Node upNode;
+  private final Node downNode;
+  private final Branch branch;
+
+  public UsualEdge(Node upNode, Node downNode, Branch branch) {
+    this.upNode = upNode;
+    this.downNode = downNode;
+    this.branch = branch;
+  }
+
+  @NotNull
+  @Override
+  public Node getUpNode() {
+    return upNode;
+  }
+
+  @NotNull
+  @Override
+  public Node getDownNode() {
+    return downNode;
+  }
+
+  @NotNull
+  @Override
+  public EdgeType getType() {
+    return EdgeType.USUAL;
+  }
+
+  @NotNull
+  @Override
+  public Branch getBranch() {
+    return branch;
+  }
+
+  @Override
+  public Node getNode() {
+    return null;
+  }
+
+  @Override
+  public Edge getEdge() {
+    return this;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/FragmentManager.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/FragmentManager.java
new file mode 100644 (file)
index 0000000..7e654e0
--- /dev/null
@@ -0,0 +1,48 @@
+package org.hanuna.gitalk.graphmodel;
+
+import com.intellij.util.Function;
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Set;
+
+/**
+ * @author erokhins
+ */
+public interface FragmentManager {
+
+  @NotNull
+  public Set<Node> allCommitsCurrentBranch(@NotNull GraphElement graphElement);
+
+  public Set<Node> getUpNodes(@NotNull GraphElement graphElement);
+
+  @Nullable
+  public GraphFragment relateFragment(@NotNull GraphElement graphElement);
+
+  @NotNull
+  public UpdateRequest changeVisibility(@NotNull GraphFragment fragment);
+
+  //true, if node is unconcealedNode
+  public void setUnconcealedNodeFunction(@NotNull Function<Node, Boolean> isUnconcealedNode);
+
+  void hideAll();
+
+  void showAll();
+
+  @NotNull
+  public GraphPreDecorator getGraphPreDecorator();
+
+  public interface GraphPreDecorator {
+    public boolean isVisibleNode(@NotNull Node node);
+
+    @Nullable
+    public Edge getHideFragmentUpEdge(@NotNull Node node);
+
+    @Nullable
+    public Edge getHideFragmentDownEdge(@NotNull Node node);
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/GraphFragment.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/GraphFragment.java
new file mode 100644 (file)
index 0000000..2707562
--- /dev/null
@@ -0,0 +1,21 @@
+package org.hanuna.gitalk.graphmodel;
+
+import org.hanuna.gitalk.graph.elements.Node;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * @author erokhins
+ */
+public interface GraphFragment {
+
+  @NotNull
+  public Node getUpNode();
+
+  @NotNull
+  public Node getDownNode();
+
+  @NotNull
+  public Collection<Node> getIntermediateNodes();
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/GraphModel.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/GraphModel.java
new file mode 100644 (file)
index 0000000..8a7f3a5
--- /dev/null
@@ -0,0 +1,31 @@
+package org.hanuna.gitalk.graphmodel;
+
+import com.intellij.util.Consumer;
+import com.intellij.util.Function;
+import com.intellij.vcs.log.CommitParents;
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public interface GraphModel {
+
+  @NotNull
+  public Graph getGraph();
+
+  public void appendCommitsToGraph(@NotNull List<? extends CommitParents> commitParentses);
+
+  public void setVisibleBranchesNodes(@NotNull Function<Node, Boolean> isStartedNode);
+
+  @NotNull
+  public FragmentManager getFragmentManager();
+
+  public void addUpdateListener(@NotNull Consumer<UpdateRequest> listener);
+
+  public void removeAllListeners();
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/FragmentGenerator.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/FragmentGenerator.java
new file mode 100644 (file)
index 0000000..9b48ddb
--- /dev/null
@@ -0,0 +1,167 @@
+package org.hanuna.gitalk.graphmodel.fragment;
+
+import com.intellij.util.Function;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.hanuna.gitalk.graphmodel.fragment.elements.SimpleGraphFragment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * @author erokhins
+ */
+public class FragmentGenerator {
+  private static final int SEARCH_LIMIT = 20; // 20 nodes
+
+  private final ShortFragmentGenerator shortFragmentGenerator;
+  private Function<Node, Boolean> isUnhiddenNodes = new Function<Node, Boolean>() {
+    @NotNull
+    @Override
+    public Boolean fun(@NotNull Node key) {
+      return false;
+    }
+  };
+
+  public FragmentGenerator(Graph graph) {
+    shortFragmentGenerator = new ShortFragmentGenerator(graph);
+  }
+
+  public void setUnconcealedNodeFunction(Function<Node, Boolean> isUnconcealedNode) {
+    shortFragmentGenerator.setUnconcealedNodeFunction(isUnconcealedNode);
+    this.isUnhiddenNodes = isUnconcealedNode;
+  }
+
+  @NotNull
+  public Set<Node> allCommitsCurrentBranch(@NotNull Node node) {
+    Set<Node> nodes = new HashSet<Node>();
+    //down walk
+    Set<Node> downNodes = new HashSet<Node>();
+    downNodes.add(node);
+    while (!downNodes.isEmpty()) {
+      Iterator<Node> iteratorNode = downNodes.iterator();
+      Node nextNode = iteratorNode.next();
+      iteratorNode.remove();
+      if (nodes.add(nextNode)) {
+        for (Edge edge: nextNode.getDownEdges()) {
+          downNodes.add(edge.getDownNode());
+        }
+      }
+    }
+
+    Set<Node> upNodes = new HashSet<Node>();
+    upNodes.add(node);
+    nodes.remove(node); // for start process
+    while (!upNodes.isEmpty()) {
+      Iterator<Node> nodeIterator = upNodes.iterator();
+      Node nextNode = nodeIterator.next();
+      nodeIterator.remove();
+      if (nodes.add(nextNode)) {
+        for (Edge edge: nextNode.getUpEdges()) {
+          upNodes.add(edge.getUpNode());
+        }
+      }
+    }
+
+    return nodes;
+  }
+
+  @NotNull
+  public Set<Node> getUpNodes(@NotNull Node node) {
+    Set<Node> nodes = new HashSet<Node>();
+
+    Set<Node> upNodes = new HashSet<Node>();
+    upNodes.add(node);
+    while (!upNodes.isEmpty()) {
+      Iterator<Node> nodeIterator = upNodes.iterator();
+      Node nextNode = nodeIterator.next();
+      nodeIterator.remove();
+      if (nodes.add(nextNode)) {
+        for (Edge edge: nextNode.getUpEdges()) {
+          upNodes.add(edge.getUpNode());
+        }
+      }
+    }
+
+    return nodes;
+  }
+
+  public GraphFragment getFragment(@NotNull Node node) {
+    int countTry = SEARCH_LIMIT;
+    GraphFragment downFragment = null;
+    while (countTry > 0 && ((downFragment = getMaximumDownFragment(node)) == null)) {
+      countTry--;
+      if (node.getUpEdges().isEmpty()) {
+        return null;
+      }
+      else {
+        node = node.getUpEdges().get(0).getUpNode();
+      }
+    }
+    if (downFragment == null) {
+      return null;
+    }
+    GraphFragment upFragment = getMaximumUpFragment(node);
+    if (upFragment == null) {
+      return downFragment;
+    }
+    else {
+      Set<Node> intermediateNodes = new HashSet<Node>(downFragment.getIntermediateNodes());
+      intermediateNodes.addAll(upFragment.getIntermediateNodes());
+      intermediateNodes.add(node);
+      return new SimpleGraphFragment(upFragment.getUpNode(), downFragment.getDownNode(), intermediateNodes);
+    }
+  }
+
+  @Nullable
+  public GraphFragment getMaximumDownFragment(@NotNull Node startNode) {
+    if (startNode.getType() != Node.NodeType.COMMIT_NODE) {
+      return null;
+    }
+    GraphFragment fragment = shortFragmentGenerator.getDownShortFragment(startNode);
+    if (fragment == null) {
+      return null;
+    }
+    Set<Node> intermediateNodes = new HashSet<Node>(fragment.getIntermediateNodes());
+    Node endNode = fragment.getDownNode();
+    while ((fragment = shortFragmentGenerator.getDownShortFragment(endNode)) != null && !isUnhiddenNodes.fun(endNode)) {
+      intermediateNodes.addAll(fragment.getIntermediateNodes());
+      intermediateNodes.add(endNode);
+      endNode = fragment.getDownNode();
+    }
+    if (intermediateNodes.isEmpty()) {
+      return null;
+    }
+    else {
+      return new SimpleGraphFragment(startNode, endNode, intermediateNodes);
+    }
+  }
+
+
+  @Nullable
+  public GraphFragment getMaximumUpFragment(@NotNull Node startNode) {
+    GraphFragment fragment = shortFragmentGenerator.getUpShortFragment(startNode);
+    if (fragment == null) {
+      return null;
+    }
+    Set<Node> intermediateNodes = new HashSet<Node>(fragment.getIntermediateNodes());
+    Node endNode = fragment.getDownNode();
+    while ((fragment = shortFragmentGenerator.getUpShortFragment(endNode)) != null && !isUnhiddenNodes.fun(endNode)) {
+      intermediateNodes.addAll(fragment.getIntermediateNodes());
+      intermediateNodes.add(endNode);
+      endNode = fragment.getUpNode();
+    }
+    if (intermediateNodes.isEmpty()) {
+      return null;
+    }
+    else {
+      return new SimpleGraphFragment(endNode, startNode, intermediateNodes);
+    }
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/FragmentManagerImpl.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/FragmentManagerImpl.java
new file mode 100644 (file)
index 0000000..1c31b68
--- /dev/null
@@ -0,0 +1,197 @@
+package org.hanuna.gitalk.graphmodel.fragment;
+
+import com.intellij.util.Function;
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.hanuna.gitalk.graphmodel.FragmentManager;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.hanuna.gitalk.graphmodel.fragment.elements.SimpleGraphFragment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * @author erokhins
+ */
+public class FragmentManagerImpl implements FragmentManager {
+  private final MutableGraph graph;
+  private final FragmentGenerator fragmentGenerator;
+  private final GraphFragmentController graphDecorator = new GraphFragmentController();
+  private final CallBackFunction callBackFunction;
+
+  private boolean updateFlag = true;
+
+
+  public FragmentManagerImpl(MutableGraph graph, CallBackFunction callBackFunction) {
+    this.graph = graph;
+    this.callBackFunction = callBackFunction;
+    fragmentGenerator = new FragmentGenerator(graph);
+  }
+
+  public interface CallBackFunction {
+    public UpdateRequest runIntermediateUpdate(@NotNull Node upNode, @NotNull Node downNode);
+
+    public void fullUpdate();
+  }
+
+  @Override
+  public void setUnconcealedNodeFunction(@NotNull Function<Node, Boolean> isUnconcealedNode) {
+    fragmentGenerator.setUnconcealedNodeFunction(isUnconcealedNode);
+  }
+
+  @NotNull
+  @Override
+  public GraphPreDecorator getGraphPreDecorator() {
+    return graphDecorator;
+  }
+
+  @Nullable
+  private Edge getHideEdge(@NotNull Node node) {
+    for (Edge edge : node.getDownEdges()) {
+      if (edge.getType() == Edge.EdgeType.HIDE_FRAGMENT) {
+        return edge;
+      }
+    }
+    for (Edge edge : node.getUpEdges()) {
+      if (edge.getType() == Edge.EdgeType.HIDE_FRAGMENT) {
+        return edge;
+      }
+    }
+    return null;
+  }
+
+  @NotNull
+  private Node getUpNode(@NotNull GraphElement graphElement) {
+    Node node = graphElement.getNode();
+    if (node == null) {
+      node = graphElement.getEdge().getUpNode();
+    }
+    return node;
+  }
+
+  @NotNull
+  @Override
+  public Set<Node> allCommitsCurrentBranch(@NotNull GraphElement graphElement) {
+    return fragmentGenerator.allCommitsCurrentBranch(getUpNode(graphElement));
+  }
+
+  @Override
+  public Set<Node> getUpNodes(@NotNull GraphElement graphElement) {
+    return fragmentGenerator.getUpNodes(getUpNode(graphElement));
+  }
+
+
+
+  @Nullable
+  @Override
+  public GraphFragment relateFragment(@NotNull GraphElement graphElement) {
+    Node node = graphElement.getNode();
+    if (node != null) {
+      Edge edge = getHideEdge(node);
+      if (edge != null) {
+        return new SimpleGraphFragment(edge.getUpNode(), edge.getDownNode(), Collections.<Node>emptySet());
+      }
+      else {
+        GraphFragment fragment = fragmentGenerator.getFragment(node);
+        if (fragment != null && fragment.getDownNode().getRowIndex() >= node.getRowIndex()) {
+          return fragment;
+        }
+        else {
+          return null;
+        }
+      }
+    }
+    else {
+      Edge edge = graphElement.getEdge();
+      assert edge != null : "bad graphElement: edge & node is null";
+      if (edge.getType() == Edge.EdgeType.HIDE_FRAGMENT) {
+        return new SimpleGraphFragment(edge.getUpNode(), edge.getDownNode(), Collections.<Node>emptySet());
+      }
+      else {
+        GraphFragment fragment = fragmentGenerator.getFragment(edge.getUpNode());
+        if (fragment != null && fragment.getDownNode().getRowIndex() >= edge.getDownNode().getRowIndex()) {
+          return fragment;
+        }
+        else {
+          return null;
+        }
+      }
+    }
+  }
+
+
+  @NotNull
+  @Override
+  public UpdateRequest changeVisibility(@NotNull GraphFragment fragment) {
+    if (fragment.getIntermediateNodes().isEmpty()) {
+      return show(fragment);
+    }
+    else {
+      return hide(fragment);
+    }
+  }
+
+  @NotNull
+  public UpdateRequest show(@NotNull GraphFragment fragment) {
+    graphDecorator.show(fragment);
+
+    if (updateFlag) {
+      return callBackFunction.runIntermediateUpdate(fragment.getUpNode(), fragment.getDownNode());
+    }
+    else {
+      return UpdateRequest.ID_UpdateRequest;
+    }
+  }
+
+  @NotNull
+  public UpdateRequest hide(@NotNull GraphFragment fragment) {
+    graphDecorator.hide(fragment);
+
+    if (updateFlag) {
+      return callBackFunction.runIntermediateUpdate(fragment.getUpNode(), fragment.getDownNode());
+    }
+    else {
+      return UpdateRequest.ID_UpdateRequest;
+    }
+  }
+
+
+  @Nullable
+  private Node commitNodeInRow(int rowIndex) {
+    for (Node node : graph.getNodeRows().get(rowIndex).getNodes()) {
+      if (node.getType() == Node.NodeType.COMMIT_NODE) {
+        return node;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public void hideAll() {
+    int rowIndex = 0;
+    updateFlag = false;
+    while (rowIndex < graph.getNodeRows().size()) {
+      Node node = commitNodeInRow(rowIndex);
+      if (node != null) {
+        GraphFragment fragment = fragmentGenerator.getMaximumDownFragment(node);
+        if (fragment != null) {
+          hide(fragment);
+        }
+      }
+      rowIndex++;
+    }
+    updateFlag = true;
+    callBackFunction.fullUpdate();
+  }
+
+  @Override
+  public void showAll() {
+    graphDecorator.showAll();
+    callBackFunction.fullUpdate();
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/GraphFragmentController.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/GraphFragmentController.java
new file mode 100644 (file)
index 0000000..0152140
--- /dev/null
@@ -0,0 +1,91 @@
+package org.hanuna.gitalk.graphmodel.fragment;
+
+import com.intellij.util.containers.MultiMap;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graphmodel.FragmentManager;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.hanuna.gitalk.graphmodel.fragment.elements.HideFragmentEdge;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author erokhins
+ */
+public class GraphFragmentController implements FragmentManager.GraphPreDecorator {
+  private final Set<Node> hideNodes = new HashSet<Node>();
+  private final Map<Edge, GraphFragment> hideFragments = new HashMap<Edge, GraphFragment>();
+  private final MultiMap<Node, Edge> upNodeEdges = new MultiMap<Node, Edge>();
+  private final MultiMap<Node, Edge> downNodeEdges = new MultiMap<Node, Edge>();
+
+  @Override
+  public boolean isVisibleNode(@NotNull Node node) {
+    return !hideNodes.contains(node);
+  }
+
+  @Override
+  @Nullable
+  public Edge getHideFragmentUpEdge(@NotNull Node node) {
+    Collection<Edge> edges = downNodeEdges.get(node);
+    for (Edge edge : edges) {
+      if (isVisibleNode(edge.getUpNode())) {
+        return edge;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  @Nullable
+  public Edge getHideFragmentDownEdge(@NotNull Node node) {
+    Collection<Edge> edges = upNodeEdges.get(node);
+    for (Edge edge : edges) {
+      if (isVisibleNode(edge.getDownNode())) {
+        return edge;
+      }
+    }
+    return null;
+  }
+
+  @NotNull
+  private Edge getHideFragmentEdge(@NotNull GraphFragment fragment) {
+    if (!fragment.getIntermediateNodes().isEmpty()) {
+      throw new IllegalArgumentException("is not hide fragment: " + fragment);
+    }
+    List<Edge> downEdges = fragment.getUpNode().getDownEdges();
+    if (downEdges.size() == 1) {
+      Edge edge = downEdges.get(0);
+      if (edge.getType() == Edge.EdgeType.HIDE_FRAGMENT && edge.getDownNode() == fragment.getDownNode()) {
+        return edge;
+      }
+    }
+    throw new IllegalArgumentException("is bad hide fragment: " + fragment);
+  }
+
+  public void show(@NotNull GraphFragment fragment) {
+    Edge hideFragmentEdge = getHideFragmentEdge(fragment);
+    upNodeEdges.removeValue(hideFragmentEdge.getUpNode(), hideFragmentEdge);
+    downNodeEdges.removeValue(hideFragmentEdge.getDownNode(), hideFragmentEdge);
+
+    GraphFragment hideFragment = hideFragments.remove(hideFragmentEdge);
+    hideNodes.removeAll(hideFragment.getIntermediateNodes());
+  }
+
+  public void hide(@NotNull GraphFragment fragment) {
+    Edge edge = new HideFragmentEdge(fragment.getUpNode(), fragment.getDownNode());
+    upNodeEdges.putValue(edge.getUpNode(), edge);
+    downNodeEdges.putValue(edge.getDownNode(), edge);
+    hideNodes.addAll(fragment.getIntermediateNodes());
+    hideFragments.put(edge, fragment);
+  }
+
+  public void showAll() {
+    hideNodes.clear();
+    hideFragments.clear();
+    upNodeEdges.clear();
+    downNodeEdges.clear();
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/ShortFragmentGenerator.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/ShortFragmentGenerator.java
new file mode 100644 (file)
index 0000000..f35ee58
--- /dev/null
@@ -0,0 +1,170 @@
+package org.hanuna.gitalk.graphmodel.fragment;
+
+import com.intellij.util.Function;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.hanuna.gitalk.graphmodel.fragment.elements.SimpleGraphFragment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author erokhins
+ */
+public class ShortFragmentGenerator {
+  private final static int MAX_FRAGMENT_SIZE = 100;
+
+  private final Graph graph;
+  private Function<Node, Boolean> isUnconcealedNodes = new Function<Node, Boolean>() {
+    @NotNull
+    @Override
+    public Boolean fun(@NotNull Node key) {
+      return false;
+    }
+  };
+
+  public ShortFragmentGenerator(Graph graph) {
+    this.graph = graph;
+  }
+
+  public void setUnconcealedNodeFunction(Function<Node, Boolean> isUnconcealedNodes) {
+    this.isUnconcealedNodes = isUnconcealedNodes;
+  }
+
+  private void addDownNodeToSet(@NotNull Set<Node> nodes, @NotNull Node node) {
+    for (Edge edge : node.getDownEdges()) {
+      Node downNode = edge.getDownNode();
+      nodes.add(downNode);
+    }
+  }
+
+  private boolean allUpNodeHere(@NotNull Set<Node> here, @NotNull Node node) {
+    for (Edge upEdge : node.getUpEdges()) {
+      if (!here.contains(upEdge.getUpNode())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Nullable
+  public GraphFragment getDownShortFragment(@NotNull Node startNode) {
+    if (startNode.getType() == Node.NodeType.EDGE_NODE) {
+      throw new IllegalArgumentException("small fragment may start only with COMMIT_NODE, but this node is: " + startNode);
+    }
+
+    Set<Node> upNodes = new HashSet<Node>();
+    upNodes.add(startNode);
+    Set<Node> notAddedNodes = new HashSet<Node>();
+    addDownNodeToSet(notAddedNodes, startNode);
+
+    Node endNode = null;
+
+    int startRowIndex = startNode.getRowIndex() + 1;
+    int lastIndex = graph.getNodeRows().size() - 1;
+
+    int countNodes = 0;
+    boolean isEnd = false;
+    for (int currentRowIndex = startRowIndex; currentRowIndex <= lastIndex && !isEnd; currentRowIndex++) {
+      for (Node node : graph.getNodeRows().get(currentRowIndex).getNodes()) {
+        if (notAddedNodes.remove(node)) {
+          countNodes++;
+          if (countNodes > MAX_FRAGMENT_SIZE) {
+            isEnd = true;
+            break;
+          }
+          if (notAddedNodes.isEmpty() && node.getType() == Node.NodeType.COMMIT_NODE) {
+            if (allUpNodeHere(upNodes, node)) { // i.e. we found smallFragment
+              endNode = node;
+            }
+            isEnd = true;
+            break;
+          }
+          else {
+            if (!allUpNodeHere(upNodes, node) || isUnconcealedNodes.fun(node)) {
+              isEnd = true;
+            }
+            upNodes.add(node);
+            addDownNodeToSet(notAddedNodes, node);
+          }
+        }
+      }
+    }
+    if (endNode == null) {
+      return null;
+    }
+    else {
+      upNodes.remove(startNode);
+      return new SimpleGraphFragment(startNode, endNode, upNodes);
+    }
+  }
+
+
+  private void addUpNodeToSet(@NotNull Set<Node> nodes, @NotNull Node node) {
+    for (Edge edge : node.getUpEdges()) {
+      Node upNode = edge.getUpNode();
+      nodes.add(upNode);
+    }
+  }
+
+  private boolean allDownNodeHere(@NotNull Set<Node> here, @NotNull Node node) {
+    for (Edge downEdge : node.getDownEdges()) {
+      if (!here.contains(downEdge.getDownNode())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Nullable
+  public GraphFragment getUpShortFragment(@NotNull Node startNode) {
+    if (startNode.getType() == Node.NodeType.EDGE_NODE) {
+      throw new IllegalArgumentException("small fragment may start only with COMMIT_NODE, but this node is: " + startNode);
+    }
+
+    Set<Node> downNodes = new HashSet<Node>();
+    downNodes.add(startNode);
+    Set<Node> notAddedNodes = new HashSet<Node>();
+    addUpNodeToSet(notAddedNodes, startNode);
+
+    Node endNode = null;
+
+    int startRowIndex = startNode.getRowIndex() - 1;
+    int lastIndex = 0;
+
+    boolean isEnd = false;
+    for (int currentRowIndex = startRowIndex; currentRowIndex >= lastIndex && !isEnd; currentRowIndex--) {
+      for (Node node : graph.getNodeRows().get(currentRowIndex).getNodes()) {
+        if (notAddedNodes.remove(node)) {
+          if (notAddedNodes.isEmpty() && node.getType() == Node.NodeType.COMMIT_NODE) {
+            if (allDownNodeHere(downNodes, node)) { // i.e. we found smallFragment
+              endNode = node;
+            }
+            isEnd = true;
+            break;
+          }
+          else {
+            if (!allDownNodeHere(downNodes, node) || isUnconcealedNodes.fun(node)) {
+              isEnd = true;
+            }
+            downNodes.add(node);
+            addUpNodeToSet(notAddedNodes, node);
+          }
+        }
+      }
+    }
+    if (endNode == null) {
+      return null;
+    }
+    else {
+      downNodes.remove(startNode);
+      return new SimpleGraphFragment(endNode, startNode, downNodes);
+    }
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/elements/HideFragmentEdge.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/elements/HideFragmentEdge.java
new file mode 100644 (file)
index 0000000..a52eb5f
--- /dev/null
@@ -0,0 +1,53 @@
+package org.hanuna.gitalk.graphmodel.fragment.elements;
+
+import org.hanuna.gitalk.graph.elements.Branch;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public class HideFragmentEdge implements Edge {
+  private final Node upNode;
+  private final Node downNode;
+
+  public HideFragmentEdge(Node upNode, Node downNode) {
+    this.upNode = upNode;
+    this.downNode = downNode;
+  }
+
+  @NotNull
+  @Override
+  public Node getUpNode() {
+    return upNode;
+  }
+
+  @NotNull
+  @Override
+  public Node getDownNode() {
+    return downNode;
+  }
+
+  @NotNull
+  @Override
+  public EdgeType getType() {
+    return EdgeType.HIDE_FRAGMENT;
+  }
+
+  @NotNull
+  @Override
+  public Branch getBranch() {
+    return upNode.getBranch();
+  }
+
+  @Override
+  public Node getNode() {
+    return null;
+  }
+
+  @Override
+  public Edge getEdge() {
+    return this;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/elements/SimpleGraphFragment.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/fragment/elements/SimpleGraphFragment.java
new file mode 100644 (file)
index 0000000..31e02b2
--- /dev/null
@@ -0,0 +1,45 @@
+package org.hanuna.gitalk.graphmodel.fragment.elements;
+
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * @author erokhins
+ *         <p/>
+ *         Conventions:
+ *         if IntermediateNodes.isEmpty() then this fragment is hde freagment and
+ *         upNode and downNode connected with HIDE_FRAGMENT_EDGE
+ */
+public class SimpleGraphFragment implements GraphFragment {
+  private final Node upNode;
+  private final Node downNode;
+  private final Collection<Node> intermediateNodes;
+
+  public SimpleGraphFragment(Node upNode, Node downNode, Collection<Node> intermediateNodes) {
+    this.upNode = upNode;
+    this.downNode = downNode;
+    this.intermediateNodes = intermediateNodes;
+  }
+
+
+  @Override
+  @NotNull
+  public Node getUpNode() {
+    return upNode;
+  }
+
+  @Override
+  @NotNull
+  public Node getDownNode() {
+    return downNode;
+  }
+
+  @Override
+  @NotNull
+  public Collection<Node> getIntermediateNodes() {
+    return intermediateNodes;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/BadVisibleEdgeNodeFixer.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/BadVisibleEdgeNodeFixer.java
new file mode 100644 (file)
index 0000000..911c9ae
--- /dev/null
@@ -0,0 +1,74 @@
+package org.hanuna.gitalk.graphmodel.impl;
+
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.GraphDecorator;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNode;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * @author erokhins
+ */
+public class BadVisibleEdgeNodeFixer implements GraphDecorator {
+  private final GraphDecorator decorator;
+
+  public BadVisibleEdgeNodeFixer(GraphDecorator decorator) {
+    this.decorator = decorator;
+  }
+
+
+  private boolean isVisibleBadEdgeNode(@NotNull Node node) {
+    if (node.getType() != Node.NodeType.EDGE_NODE || decorator.isVisibleNode(node)) {
+      return false;
+    }
+    boolean isBad = false;
+    for (Edge edge : ((MutableNode)node).getInnerUpEdges()) {
+      Node upNode = edge.getUpNode();
+      if (!decorator.isVisibleNode(upNode) || isVisibleBadEdgeNode(upNode)) {
+        isBad = true;
+      }
+    }
+    return isBad;
+  }
+
+  @Override
+  public boolean isVisibleNode(@NotNull Node node) {
+    if (isVisibleBadEdgeNode(node)) {
+      return false;
+    }
+    return decorator.isVisibleNode(node);
+  }
+
+  @NotNull
+  private Node getDownNode(@NotNull Node edgeNode) {
+    return decorator.getDownEdges(edgeNode, ((MutableNode)edgeNode).getInnerDownEdges()).get(0).getDownNode();
+  }
+
+
+  @NotNull
+  @Override
+  public List<Edge> getDownEdges(@NotNull Node node, @NotNull List<Edge> innerDownEdges) {
+    List<Edge> prevDownEdges = decorator.getDownEdges(node, innerDownEdges);
+    for (ListIterator<Edge> edgeIterator = prevDownEdges.listIterator(); edgeIterator.hasNext(); ) {
+      Edge edge = edgeIterator.next();
+      if (isVisibleBadEdgeNode(edge.getDownNode())) {
+        Node downNode = edge.getDownNode();
+        while (isVisibleBadEdgeNode(downNode)) {
+          downNode = getDownNode(downNode);
+        }
+        Edge newEdge = null;
+        edgeIterator.set(newEdge);
+      }
+    }
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  @NotNull
+  @Override
+  public List<Edge> getUpEdges(@NotNull Node node, @NotNull List<Edge> innerUpEdges) {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/BranchVisibleNodes.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/BranchVisibleNodes.java
new file mode 100644 (file)
index 0000000..2415433
--- /dev/null
@@ -0,0 +1,55 @@
+package org.hanuna.gitalk.graphmodel.impl;
+
+import com.intellij.util.Function;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNode;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNodeRow;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author erokhins
+ */
+public class BranchVisibleNodes {
+  private final MutableGraph graph;
+  private Set<Node> visibleNodes = Collections.emptySet();
+
+  public BranchVisibleNodes(MutableGraph graph) {
+    this.graph = graph;
+  }
+
+  public Set<Node> generateVisibleBranchesNodes(@NotNull Function<Node, Boolean> isStartedNode) {
+    Set<Node> visibleNodes = new HashSet<Node>();
+    for (MutableNodeRow row : graph.getAllRows()) {
+      for (MutableNode node : row.getInnerNodeList()) {
+        if (isStartedNode.fun(node)) {
+          visibleNodes.add(node);
+        }
+        if (isStartedNode.fun(node) || visibleNodes.contains(node)) {
+          for (Edge edge : node.getInnerDownEdges()) {
+            visibleNodes.add(edge.getDownNode());
+          }
+        }
+      }
+    }
+    return visibleNodes;
+  }
+
+  public void setVisibleNodes(@NotNull Set<Node> visibleNodes) {
+    this.visibleNodes = visibleNodes;
+  }
+
+  public Set<Node> getVisibleNodes() {
+    return Collections.unmodifiableSet(visibleNodes);
+  }
+
+  public boolean isVisibleNode(@NotNull Node node) {
+    return visibleNodes.contains(node);
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/GraphBranchShowFixer.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/GraphBranchShowFixer.java
new file mode 100644 (file)
index 0000000..4389e23
--- /dev/null
@@ -0,0 +1,95 @@
+package org.hanuna.gitalk.graphmodel.impl;
+
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNode;
+import org.hanuna.gitalk.graph.mutable.elements.MutableNodeRow;
+import org.hanuna.gitalk.graphmodel.FragmentManager;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.hanuna.gitalk.graphmodel.fragment.FragmentManagerImpl;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author erokhins
+ */
+public class GraphBranchShowFixer {
+  private final MutableGraph graph;
+  private final FragmentManagerImpl fragmentManager;
+
+  private Set<Node> prevVisibleNodes;
+  private Set<Node> newVisibleNodes;
+  private FragmentManager.GraphPreDecorator fragmentGraphDecorator;
+
+  public GraphBranchShowFixer(MutableGraph graph, FragmentManagerImpl fragmentManager) {
+    this.graph = graph;
+    this.fragmentManager = fragmentManager;
+  }
+
+  public void fixCrashBranches(@NotNull Set<Node> prevVisibleNodes, @NotNull Set<Node> newVisibleNodes) {
+    this.prevVisibleNodes = prevVisibleNodes;
+    this.newVisibleNodes = newVisibleNodes;
+    fragmentGraphDecorator = fragmentManager.getGraphPreDecorator();
+    Set<Node> badHiddenNode = new HashSet<Node>();
+    for (MutableNodeRow row : graph.getAllRows()) {
+      for (MutableNode node : row.getInnerNodeList()) {
+        if (isEssentialNode(node)) {
+          badHiddenNode.addAll(badHiddenNodes(node));
+        }
+      }
+    }
+    showNodes(badHiddenNode);
+  }
+
+  private Set<Node> badHiddenNodes(@NotNull MutableNode node) {
+    if (fragmentGraphDecorator.getHideFragmentDownEdge(node) != null) {
+      return Collections.emptySet();
+    }
+    Set<Node> badHiddenNodes = new HashSet<Node>();
+    for (Edge edge : node.getInnerDownEdges()) {
+      Node downNode = edge.getDownNode();
+      if (!fragmentGraphDecorator.isVisibleNode(downNode)) {
+        badHiddenNodes.add(downNode);
+      }
+    }
+    return badHiddenNodes;
+  }
+
+  private boolean isEssentialNode(@NotNull Node node) {
+    if (newVisibleNodes.contains(node) && !prevVisibleNodes.contains(node)) {
+      if (fragmentGraphDecorator.isVisibleNode(node)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @NotNull
+  private Edge getExternalHideEdge(@NotNull Node node) {
+    while (!fragmentGraphDecorator.isVisibleNode(node)) {
+      MutableNode thisNode = (MutableNode)node;
+      if (thisNode.getInnerDownEdges().size() == 0) {
+        throw new IllegalStateException("not found down visible node of hide node");
+      }
+      node = thisNode.getInnerDownEdges().get(0).getDownNode();
+    }
+    return fragmentGraphDecorator.getHideFragmentUpEdge(node);
+  }
+
+  private void showNodes(Set<Node> nodes) {
+    for (Node node : nodes) {
+      while (!fragmentGraphDecorator.isVisibleNode(node)) {
+        Edge hideEdge = getExternalHideEdge(node);
+        GraphFragment fragment = fragmentManager.relateFragment(hideEdge);
+        assert fragment != null : "bad hide edge" + hideEdge;
+        fragmentManager.show(fragment);
+      }
+    }
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/GraphDecoratorImpl.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/GraphDecoratorImpl.java
new file mode 100644 (file)
index 0000000..e97e1fa
--- /dev/null
@@ -0,0 +1,61 @@
+package org.hanuna.gitalk.graphmodel.impl;
+
+import com.intellij.util.Function;
+import com.intellij.util.SmartList;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.GraphDecorator;
+import org.hanuna.gitalk.graphmodel.FragmentManager;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+
+public class GraphDecoratorImpl implements GraphDecorator {
+  private final FragmentManager.GraphPreDecorator preDecorator;
+  private final Function<Node, Boolean> branchVisibleNodes;
+
+  public GraphDecoratorImpl(FragmentManager.GraphPreDecorator preDecorator, Function<Node, Boolean> branchVisibleNodes) {
+    this.preDecorator = preDecorator;
+    this.branchVisibleNodes = branchVisibleNodes;
+  }
+
+  @Override
+  public boolean isVisibleNode(@NotNull Node node) {
+    return preDecorator.isVisibleNode(node) && branchVisibleNodes.fun(node);
+  }
+
+  @NotNull
+  @Override
+  public List<Edge> getDownEdges(@NotNull Node node, @NotNull List<Edge> innerDownEdges) {
+    Edge edge = preDecorator.getHideFragmentDownEdge(node);
+    if (edge != null) {
+      return new SmartList<Edge>(edge);
+    }
+    else {
+      return Collections.unmodifiableList(innerDownEdges);
+    }
+  }
+
+  @NotNull
+  @Override
+  public List<Edge> getUpEdges(@NotNull Node node, @NotNull List<Edge> innerUpEdges) {
+    Edge hideFragmentUpEdge = preDecorator.getHideFragmentUpEdge(node);
+    if (hideFragmentUpEdge != null) {
+      return new SmartList<Edge>(hideFragmentUpEdge);
+    }
+
+    List<Edge> edges = new ArrayList<Edge>();
+    for (Edge edge : innerUpEdges) {
+      if (isVisibleNode(edge.getUpNode())) {
+        edges.add(edge);
+      }
+    }
+    return edges;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/GraphModelImpl.java b/platform/dvcs/vcs-log/vcs-log-graph/src/org/hanuna/gitalk/graphmodel/impl/GraphModelImpl.java
new file mode 100644 (file)
index 0000000..5513f0c
--- /dev/null
@@ -0,0 +1,134 @@
+package org.hanuna.gitalk.graphmodel.impl;
+
+import com.intellij.util.Consumer;
+import com.intellij.util.Function;
+import com.intellij.vcs.log.CommitParents;
+import com.intellij.vcs.log.Ref;
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.GraphBuilder;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.hanuna.gitalk.graphmodel.FragmentManager;
+import org.hanuna.gitalk.graphmodel.GraphModel;
+import org.hanuna.gitalk.graphmodel.fragment.FragmentManagerImpl;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author erokhins
+ */
+public class GraphModelImpl implements GraphModel {
+  private final MutableGraph graph;
+  private final Collection<Ref> myRefs;
+  private final FragmentManagerImpl fragmentManager;
+  private final BranchVisibleNodes visibleNodes;
+  private final List<Consumer<UpdateRequest>> listeners = new ArrayList<Consumer<UpdateRequest>>();
+  private final GraphBranchShowFixer branchShowFixer;
+
+  private Function<Node, Boolean> isStartedBranchVisibilityNode = new Function<Node, Boolean>() {
+    @NotNull
+    @Override
+    public Boolean fun(@NotNull Node key) {
+      return true;
+    }
+  };
+
+  public GraphModelImpl(MutableGraph graph, Collection<Ref> allRefs) {
+    this.graph = graph;
+    myRefs = allRefs;
+    this.fragmentManager = new FragmentManagerImpl(graph, new FragmentManagerImpl.CallBackFunction() {
+      @Override
+      public UpdateRequest runIntermediateUpdate(@NotNull Node upNode, @NotNull Node downNode) {
+        return GraphModelImpl.this.updateIntermediate(upNode, downNode);
+      }
+
+      @Override
+      public void fullUpdate() {
+        GraphModelImpl.this.fullUpdate();
+      }
+    });
+    this.visibleNodes = new BranchVisibleNodes(graph);
+    visibleNodes.setVisibleNodes(visibleNodes.generateVisibleBranchesNodes(isStartedBranchVisibilityNode));
+    branchShowFixer = new GraphBranchShowFixer(graph, fragmentManager);
+    graph.setGraphDecorator(new GraphDecoratorImpl(fragmentManager.getGraphPreDecorator(), new Function<Node, Boolean>() {
+      @NotNull
+      @Override
+      public Boolean fun(@NotNull Node key) {
+        return visibleNodes.isVisibleNode(key);
+      }
+    }));
+    graph.updateVisibleRows();
+  }
+
+  @NotNull
+  private UpdateRequest updateIntermediate(@NotNull Node upNode, @NotNull Node downNode) {
+    int upRowIndex = upNode.getRowIndex();
+    int downRowIndex = downNode.getRowIndex();
+    graph.updateVisibleRows();
+
+    UpdateRequest updateRequest = UpdateRequest.buildFromToInterval(upRowIndex, downRowIndex, upNode.getRowIndex(), downNode.getRowIndex());
+    callUpdateListener(updateRequest);
+    return updateRequest;
+  }
+
+  private void fullUpdate() {
+    int oldSize = graph.getNodeRows().size();
+    graph.updateVisibleRows();
+    UpdateRequest updateRequest = UpdateRequest.buildFromToInterval(0, oldSize - 1, 0, graph.getNodeRows().size() - 1);
+    callUpdateListener(updateRequest);
+  }
+
+  @NotNull
+  @Override
+  public Graph getGraph() {
+    return graph;
+  }
+
+  @Override
+  public void appendCommitsToGraph(@NotNull List<? extends CommitParents> commitParentses) {
+    int oldSize = graph.getNodeRows().size();
+    GraphBuilder.addCommitsToGraph(graph, commitParentses, myRefs);
+    visibleNodes.setVisibleNodes(visibleNodes.generateVisibleBranchesNodes(isStartedBranchVisibilityNode));
+    graph.updateVisibleRows();
+
+    UpdateRequest updateRequest = UpdateRequest.buildFromToInterval(0, oldSize - 1, 0, graph.getNodeRows().size() - 1);
+    callUpdateListener(updateRequest);
+  }
+
+  @Override
+  public void setVisibleBranchesNodes(@NotNull Function<Node, Boolean> isStartedNode) {
+    this.isStartedBranchVisibilityNode = isStartedNode;
+    Set<Node> prevVisibleNodes = visibleNodes.getVisibleNodes();
+    Set<Node> newVisibleNodes = visibleNodes.generateVisibleBranchesNodes(isStartedNode);
+    branchShowFixer.fixCrashBranches(prevVisibleNodes, newVisibleNodes);
+    visibleNodes.setVisibleNodes(newVisibleNodes);
+    fullUpdate();
+  }
+
+  @NotNull
+  @Override
+  public FragmentManager getFragmentManager() {
+    return fragmentManager;
+  }
+
+  private void callUpdateListener(@NotNull UpdateRequest updateRequest) {
+    for (Consumer<UpdateRequest> listener : listeners) {
+      listener.consume(updateRequest);
+    }
+  }
+
+  @Override
+  public void addUpdateListener(@NotNull Consumer<UpdateRequest> listener) {
+    listeners.add(listener);
+  }
+
+  @Override
+  public void removeAllListeners() {
+    listeners.clear();
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/GraphStrUtils.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/GraphStrUtils.java
new file mode 100644 (file)
index 0000000..07154a3
--- /dev/null
@@ -0,0 +1,118 @@
+package org.hanuna.gitalk.graph;
+
+import org.hanuna.gitalk.graph.elements.Branch;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class GraphStrUtils {
+
+  public static String toStr(Branch branch) {
+    if (branch.getUpCommitHash() == branch.getDownCommitHash()) {
+      return branch.getUpCommitHash().toStrHash();
+    }
+    else {
+      return branch.getUpCommitHash().toStrHash() + '#' + branch.getDownCommitHash().toStrHash();
+    }
+  }
+
+  /**
+   * @return example:
+   *         a0:a1:USUAL:a0
+   *         up:down:type:branch
+   */
+  public static String toStr(Edge edge) {
+    StringBuilder s = new StringBuilder();
+    s.append(edge.getUpNode().getCommitHash().toStrHash()).append(":");
+    s.append(edge.getDownNode().getCommitHash().toStrHash()).append(":");
+    s.append(edge.getType()).append(":");
+    s.append(toStr(edge.getBranch()));
+    return s.toString();
+  }
+
+  public static String toStr(List<Edge> edges) {
+    StringBuilder s = new StringBuilder();
+    List<String> edgeStrings = new ArrayList<String>();
+    for (Edge edge : edges) {
+      edgeStrings.add(toStr(edge));
+    }
+
+    Collections.sort(edgeStrings);
+    if (edgeStrings.size() > 0) {
+      s.append(edgeStrings.get(0));
+    }
+    for (int i = 1; i < edges.size(); i++) {
+      s.append(" ").append(edgeStrings.get(i));
+    }
+    return s.toString();
+  }
+
+
+  /**
+   * @return example:
+   *         a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0
+   *         <p/>
+   *         explanation:
+   *         getCommitHash|-upEdges|-downEdges|-NodeType|-branch|-rowIndex
+   */
+
+  public static String toStr(Node node) {
+    StringBuilder s = new StringBuilder();
+    s.append(node.getCommitHash().toStrHash()).append("|-");
+    s.append(toStr(node.getUpEdges())).append("|-");
+    s.append(toStr(node.getDownEdges())).append("|-");
+    s.append(node.getType()).append("|-");
+    s.append(toStr(node.getBranch())).append("|-");
+    s.append(node.getRowIndex());
+    return s.toString();
+  }
+
+  public static String toStr(NodeRow row) {
+    StringBuilder s = new StringBuilder();
+    List<Node> nodes = row.getNodes();
+    List<String> nodesString = new ArrayList<String>();
+    for (Node node : nodes) {
+      nodesString.add(toStr(node));
+    }
+    Collections.sort(nodesString);
+
+    if (nodesString.size() > 0) {
+      s.append(nodesString.get(0));
+    }
+    for (int i = 1; i < nodesString.size(); i++) {
+      s.append("\n   ").append(nodesString.get(i));
+    }
+    return s.toString();
+  }
+
+  /**
+   * @return textOfGraph in next format:
+   *         every row in separate line, if in row more that 1 node:
+   *         ...
+   *         first node text row
+   *         next node text row
+   *         next node text row
+   *         next row ...
+   */
+
+  public static String toStr(Graph graph) {
+    StringBuilder s = new StringBuilder();
+    List<NodeRow> rows = graph.getNodeRows();
+    if (rows.size() > 0) {
+      s.append(toStr(rows.get(0)));
+    }
+    for (int i = 1; i < rows.size(); i++) {
+      s.append("\n").append(toStr(rows.get(i)));
+    }
+    return s.toString();
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/GraphTestUtils.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/GraphTestUtils.java
new file mode 100644 (file)
index 0000000..54faa17
--- /dev/null
@@ -0,0 +1,62 @@
+package org.hanuna.gitalk.graph;
+
+import com.intellij.vcs.log.CommitParents;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+import org.hanuna.gitalk.graph.mutable.GraphBuilder;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.hanuna.gitalk.log.parser.SimpleCommitListParser;
+import com.intellij.vcs.log.Ref;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author erokhins
+ */
+public class GraphTestUtils {
+
+
+  @NotNull
+  public static Node getCommitNode(Graph graph, int rowIndex) {
+    NodeRow row = graph.getNodeRows().get(rowIndex);
+    for (Node node : row.getNodes()) {
+      if (node.getType() == Node.NodeType.COMMIT_NODE) {
+        return node;
+      }
+    }
+    throw new IllegalArgumentException();
+  }
+
+  @NotNull
+  public static MutableGraph getNewMutableGraph(@NotNull String inputStr) {
+    SimpleCommitListParser parser = new SimpleCommitListParser(new StringReader(inputStr));
+    List<CommitParents> commitParentses;
+    try {
+      commitParentses = parser.readAllCommits();
+    }
+    catch (IOException e) {
+      throw new IllegalStateException();
+    }
+    return GraphBuilder.build(commitParentses, Collections.<Ref>emptyList());
+  }
+
+  // "1 20 3" -> {1,20,3}
+  @NotNull
+  public static Set<Integer> parseIntegers(@NotNull String str) {
+    if (str.length() == 0) {
+      return Collections.emptySet();
+    }
+    String[] strings = str.split(" ");
+    Set<Integer> integers = new HashSet<Integer>();
+    for (String s : strings) {
+      integers.add(Integer.parseInt(s));
+    }
+    return integers;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/mutable/GraphAppendBuildTest.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/mutable/GraphAppendBuildTest.java
new file mode 100644 (file)
index 0000000..28c69fa
--- /dev/null
@@ -0,0 +1,132 @@
+package org.hanuna.gitalk.graph.mutable;
+
+import com.intellij.vcs.log.CommitParents;
+import org.hanuna.gitalk.log.parser.SimpleCommitListParser;
+import com.intellij.vcs.log.Ref;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static org.hanuna.gitalk.graph.GraphStrUtils.toStr;
+
+/**
+ * @author erokhins
+ */
+public class GraphAppendBuildTest {
+  public void runTest(String firstPart, String firstPartStr, String secondPart, String secondPartStr) {
+    List<CommitParents> commitParentses = SimpleCommitListParser.parseCommitList(firstPart);
+    MutableGraph graph = GraphBuilder.build(commitParentses, Collections.<Ref>emptyList());
+    assertEquals(firstPartStr, toStr(graph));
+
+    commitParentses = SimpleCommitListParser.parseCommitList(secondPart);
+    GraphBuilder.addCommitsToGraph(graph, commitParentses, Collections.<Ref>emptyList());
+    assertEquals(secondPartStr, toStr(graph));
+  }
+
+  @Test
+  public void simpleEnd() {
+    runTest("a0|-",
+
+            "a0|-|-|-COMMIT_NODE|-a0|-0",
+
+
+            "a1|-",
+
+            "a0|-|-|-COMMIT_NODE|-a0|-0\n" + "a1|-|-|-COMMIT_NODE|-a1|-1");
+  }
+
+
+  @Test
+  public void oneEndNode() {
+    runTest("a0|-a2",
+
+            "a0|-|-a0:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" + "a2|-a0:a2:USUAL:a0|-|-END_COMMIT_NODE|-a0|-1",
+
+
+            "a1|-a2\n" + "a2|-",
+
+            "a0|-|-a0:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-|-a1:a2:USUAL:a1|-COMMIT_NODE|-a1|-1\n" +
+            "a2|-a0:a2:USUAL:a0 a1:a2:USUAL:a1|-|-COMMIT_NODE|-a0|-2");
+  }
+
+
+  @Test
+  public void oneEndAfterNotAddNode() {
+    runTest("a0|-a2",
+
+            "a0|-|-a0:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" + "a2|-a0:a2:USUAL:a0|-|-END_COMMIT_NODE|-a0|-1",
+
+
+            "a1|-a2", "a0|-|-a0:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+                      "a1|-|-a1:a2:USUAL:a1|-COMMIT_NODE|-a1|-1\n" +
+                      "a2|-a0:a2:USUAL:a0 a1:a2:USUAL:a1|-|-END_COMMIT_NODE|-a0|-2");
+  }
+
+
+  @Test
+  public void oneEndImmediatelyAddNode() {
+    runTest("a0|-a1",
+
+            "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" + "a1|-a0:a1:USUAL:a0|-|-END_COMMIT_NODE|-a0|-1",
+
+
+            "a1|-",
+
+            "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" + "a1|-a0:a1:USUAL:a0|-|-COMMIT_NODE|-a0|-1"
+
+    );
+  }
+
+  @Test
+  public void oneEndImmediatelyAddNode2() {
+    runTest("a0|-a1",
+
+            "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" + "a1|-a0:a1:USUAL:a0|-|-END_COMMIT_NODE|-a0|-1",
+
+
+            "a1|-a2\n" + "a2|-",
+
+            "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-a0:a1:USUAL:a0|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+            "a2|-a1:a2:USUAL:a0|-|-COMMIT_NODE|-a0|-2"
+
+    );
+  }
+
+  @Test
+  public void edgeNodeInEndImmediately() {
+    runTest("a0|-a2\n" + "a1|-a2",
+
+            "a0|-|-a0:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-|-a1:a2:USUAL:a1|-COMMIT_NODE|-a1|-1\n" +
+            "a2|-a0:a2:USUAL:a0 a1:a2:USUAL:a1|-|-END_COMMIT_NODE|-a0|-2",
+
+
+            "a2|-",
+
+            "a0|-|-a0:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-|-a1:a2:USUAL:a1|-COMMIT_NODE|-a1|-1\n" +
+            "a2|-a0:a2:USUAL:a0 a1:a2:USUAL:a1|-|-COMMIT_NODE|-a0|-2");
+  }
+
+  @Test
+  public void edgeNodeInEnd() {
+    runTest("a0|-a3\n" + "a1|-a3",
+
+            "a0|-|-a0:a3:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-|-a1:a3:USUAL:a1|-COMMIT_NODE|-a1|-1\n" +
+            "a3|-a0:a3:USUAL:a0 a1:a3:USUAL:a1|-|-END_COMMIT_NODE|-a0|-2",
+
+
+            "a2|-a3",
+
+            "a0|-|-a0:a3:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-|-a1:a3:USUAL:a1|-COMMIT_NODE|-a1|-1\n" +
+            "a2|-|-a2:a3:USUAL:a2|-COMMIT_NODE|-a2|-2\n" +
+            "   a3|-a0:a3:USUAL:a0 a1:a3:USUAL:a1|-a3:a3:USUAL:a0|-EDGE_NODE|-a0|-2\n" +
+            "a3|-a2:a3:USUAL:a2 a3:a3:USUAL:a0|-|-END_COMMIT_NODE|-a0|-3");
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/mutable/GraphBuilderTest.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graph/mutable/GraphBuilderTest.java
new file mode 100644 (file)
index 0000000..0a78be5
--- /dev/null
@@ -0,0 +1,139 @@
+package org.hanuna.gitalk.graph.mutable;
+
+import com.intellij.vcs.log.CommitParents;
+import org.hanuna.gitalk.log.parser.SimpleCommitListParser;
+import com.intellij.vcs.log.Ref;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static org.hanuna.gitalk.graph.GraphStrUtils.toStr;
+
+/**
+ * @author erokhins
+ */
+public class GraphBuilderTest {
+
+
+  public void runTest(String input, String out) {
+    List<CommitParents> commitParentses = SimpleCommitListParser.parseCommitList(input);
+    MutableGraph graph = GraphBuilder.build(commitParentses, Collections.<Ref>emptyList());
+    assertEquals(out, toStr(graph));
+  }
+
+
+  @Test
+  public void simple1() {
+    runTest("12|-", "12|-|-|-COMMIT_NODE|-12|-0");
+  }
+
+  @Test
+  public void simple2() {
+    runTest("12|-af\n" + "af|-",
+
+            "12|-|-12:af:USUAL:12|-COMMIT_NODE|-12|-0\n" + "af|-12:af:USUAL:12|-|-COMMIT_NODE|-12|-1");
+  }
+
+  @Test
+  public void simple3() {
+    runTest("a0|-a1\n" +
+            "a1|-a2\n" +
+            "a2|-",
+
+            "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-a0:a1:USUAL:a0|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+            "a2|-a1:a2:USUAL:a0|-|-COMMIT_NODE|-a0|-2");
+  }
+
+  @Test
+  public void moreParents() {
+    runTest("a0|-a1 a2 a3\n" +
+            "a1|-\n" +
+            "a2|-\n" +
+            "a3|-",
+
+            "a0|-|-a0:a1:USUAL:a0#a1 a0:a2:USUAL:a0#a2 a0:a3:USUAL:a0#a3|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-a0:a1:USUAL:a0#a1|-|-COMMIT_NODE|-a0#a1|-1\n" +
+            "a2|-a0:a2:USUAL:a0#a2|-|-COMMIT_NODE|-a0#a2|-2\n" +
+            "a3|-a0:a3:USUAL:a0#a3|-|-COMMIT_NODE|-a0#a3|-3");
+  }
+
+  @Test
+  public void edgeNodes() {
+    runTest("a0|-a1 a3\n" +
+            "a1|-a3 a2\n" +
+            "a2|-\n" +
+            "a3|-",
+
+            "a0|-|-a0:a1:USUAL:a0#a1 a0:a3:USUAL:a0#a3|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-a0:a1:USUAL:a0#a1|-a1:a2:USUAL:a1#a2 a1:a3:USUAL:a1#a3|-COMMIT_NODE|-a0#a1|-1\n" +
+            "a2|-a1:a2:USUAL:a1#a2|-|-COMMIT_NODE|-a1#a2|-2\n" +
+            "   a3|-a0:a3:USUAL:a0#a3 a1:a3:USUAL:a1#a3|-a3:a3:USUAL:a0#a3|-EDGE_NODE|-a0#a3|-2\n" +
+            "a3|-a3:a3:USUAL:a0#a3|-|-COMMIT_NODE|-a0#a3|-3");
+  }
+
+  @Test
+  public void nodeEdge2() {
+    runTest("a0|-a1 a3\n" +
+            "a1|-a3\n" +
+            "a2|-\n" +
+            "a3|-",
+
+            "a0|-|-a0:a1:USUAL:a0#a1 a0:a3:USUAL:a0#a3|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-a0:a1:USUAL:a0#a1|-a1:a3:USUAL:a0#a1|-COMMIT_NODE|-a0#a1|-1\n" +
+            "a2|-|-|-COMMIT_NODE|-a2|-2\n" +
+            "   a3|-a0:a3:USUAL:a0#a3 a1:a3:USUAL:a0#a1|-a3:a3:USUAL:a0#a3|-EDGE_NODE|-a0#a3|-2\n" +
+            "a3|-a3:a3:USUAL:a0#a3|-|-COMMIT_NODE|-a0#a3|-3");
+  }
+
+  @Test
+  public void twoChildren() {
+    runTest("a0|-a2\n" +
+            "a1|-a2\n" +
+            "a2|-",
+
+            "a0|-|-a0:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-|-a1:a2:USUAL:a1|-COMMIT_NODE|-a1|-1\n" +
+            "a2|-a0:a2:USUAL:a0 a1:a2:USUAL:a1|-|-COMMIT_NODE|-a0|-2");
+  }
+
+  @Test
+  public void twoChildren_hard() {
+    runTest("a0|-a1 a2\n" +
+            "a1|-a2\n" +
+            "a2|-",
+
+            "a0|-|-a0:a1:USUAL:a0#a1 a0:a2:USUAL:a0#a2|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-a0:a1:USUAL:a0#a1|-a1:a2:USUAL:a0#a1|-COMMIT_NODE|-a0#a1|-1\n" +
+            "a2|-a0:a2:USUAL:a0#a2 a1:a2:USUAL:a0#a1|-|-COMMIT_NODE|-a0#a2|-2");
+  }
+
+  @Test
+  public void simpleNotFullGraph() {
+    runTest("a0|-a1",
+
+            "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" + "a1|-a0:a1:USUAL:a0|-|-END_COMMIT_NODE|-a0|-1");
+  }
+
+  @Test
+  public void notFullGraph_EdgeNode() {
+    runTest("a0|-a2\n" + "a1|-a2",
+
+            "a0|-|-a0:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-|-a1:a2:USUAL:a1|-COMMIT_NODE|-a1|-1\n" +
+            "a2|-a0:a2:USUAL:a0 a1:a2:USUAL:a1|-|-END_COMMIT_NODE|-a0|-2");
+  }
+
+
+  @Test
+  public void notFullGraph_EdgeNode_hard() {
+    runTest("a0|-a3\n" + "a1|-a2 a3",
+
+            "a0|-|-a0:a3:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+            "a1|-|-a1:a2:USUAL:a1#a2 a1:a3:USUAL:a1#a3|-COMMIT_NODE|-a1|-1\n" +
+            "a2|-a1:a2:USUAL:a1#a2|-|-END_COMMIT_NODE|-a1#a2|-2\n" +
+            "   a3|-a0:a3:USUAL:a0 a1:a3:USUAL:a1#a3|-|-END_COMMIT_NODE|-a0|-2");
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/FragmentManagerTest.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/FragmentManagerTest.java
new file mode 100644 (file)
index 0000000..dd00ee1
--- /dev/null
@@ -0,0 +1,149 @@
+package org.hanuna.gitalk.graphmodel.fragment;
+
+import com.intellij.util.Function;
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.hanuna.gitalk.graph.GraphTestUtils;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.hanuna.gitalk.graphmodel.FragmentManager;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.hanuna.gitalk.graphmodel.impl.GraphDecoratorImpl;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static org.hanuna.gitalk.graph.GraphStrUtils.toStr;
+import static org.hanuna.gitalk.graph.GraphTestUtils.getCommitNode;
+import static org.hanuna.gitalk.graphmodel.fragment.GraphModelUtils.toStr;
+
+/**
+ * @author erokhins
+ */
+public class FragmentManagerTest {
+
+    public void runTest(String inputGraph, int hideNodeRowIndex, String fragmentStr, int showNodeRowIndex, String hideGraphStr) {
+        final MutableGraph graph = GraphTestUtils.getNewMutableGraph(inputGraph);
+        FragmentManager fragmentManager = new FragmentManagerImpl(graph, new FragmentManagerImpl.CallBackFunction() {
+            @Override
+            public UpdateRequest runIntermediateUpdate(@NotNull Node upNode, @NotNull Node downNode) {
+                graph.updateVisibleRows();
+                return UpdateRequest.ID_UpdateRequest;
+            }
+
+            @Override
+            public void fullUpdate() {
+                graph.updateVisibleRows();
+            }
+        });
+        graph.setGraphDecorator(new GraphDecoratorImpl(fragmentManager.getGraphPreDecorator(), new Function<Node, Boolean>() {
+            @NotNull
+            @Override
+            public Boolean fun(@NotNull Node key) {
+                return true;
+            }
+        }));
+
+        Node node = getCommitNode(graph, hideNodeRowIndex);
+        GraphFragment fragment = fragmentManager.relateFragment(node);
+        assertEquals(fragmentStr, toStr(fragment));
+
+        if (fragment == null) {
+            return;
+        }
+
+        String saveGraphStr = toStr(graph);
+
+        fragmentManager.changeVisibility(fragment);
+        assertEquals(hideGraphStr, toStr(graph));
+
+        fragment = fragmentManager.relateFragment(getCommitNode(graph, showNodeRowIndex));
+        assertNotNull(fragment);
+
+        fragmentManager.changeVisibility(fragment);
+        assertEquals(saveGraphStr, toStr(graph));
+    }
+
+    @Test
+    public void simple1() {
+        runTest("a0|-a1\n" +
+                "a1|-a2\n" +
+                "a2|-",
+                1,
+
+                "a0:0|-a1:1|-a2:2",
+
+                0,
+
+                "a0|-|-a0:a2:HIDE_FRAGMENT:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a2|-a0:a2:HIDE_FRAGMENT:a0|-|-COMMIT_NODE|-a0|-1"
+        );
+    }
+
+    @Test
+    public void simple_null_fragment() {
+        runTest("a0|-a1\n" +
+                "a1|-",
+                1,
+
+                "null",
+
+                0,
+
+                ""
+        );
+    }
+
+    @Test
+    public void simple_with_notFullGraph() {
+        runTest("a0|-a1\n" +
+                "a1|-a2",
+                1,
+
+                "null",
+
+                0,
+
+                ""
+        );
+    }
+
+    @Test
+    public void difficultGraph1() {
+        runTest("a0|-a1 a4\n" +
+                "a1|-a2 a3\n" +
+                "a2|-a3\n" +
+                "a3|-a4\n" +
+                "a4|-",
+                2,
+
+                "a1:1|-a2:2|-a3:3",
+
+                1,
+
+                "a0|-|-a0:a1:USUAL:a0#a1 a0:a4:USUAL:a0#a4|-COMMIT_NODE|-a0|-0\n" +
+                "a1|-a0:a1:USUAL:a0#a1|-a1:a3:HIDE_FRAGMENT:a0#a1|-COMMIT_NODE|-a0#a1|-1\n" +
+                "a3|-a1:a3:HIDE_FRAGMENT:a0#a1|-a3:a4:USUAL:a1#a3|-COMMIT_NODE|-a1#a3|-2\n" +
+                "a4|-a0:a4:USUAL:a0#a4 a3:a4:USUAL:a1#a3|-|-COMMIT_NODE|-a0#a4|-3"
+        );
+    }
+
+    @Test
+    public void difficultGraph2() {
+        runTest("a0|-a1 a4\n" +
+                "a1|-a2 a3\n" +
+                "a2|-a3\n" +
+                "a3|-a4\n" +
+                "a4|-",
+                4,
+
+                "a0:0|-a1:1 a2:2 a3:3|-a4:4",
+
+                1,
+
+                "a0|-|-a0:a4:HIDE_FRAGMENT:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a4|-a0:a4:HIDE_FRAGMENT:a0|-|-COMMIT_NODE|-a0#a4|-1"
+        );
+    }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/GraphModelTest.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/GraphModelTest.java
new file mode 100644 (file)
index 0000000..198f993
--- /dev/null
@@ -0,0 +1,547 @@
+package org.hanuna.gitalk.graphmodel.fragment;
+
+import com.intellij.util.Function;
+import com.intellij.vcs.log.Ref;
+import org.hanuna.gitalk.graph.GraphTestUtils;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.hanuna.gitalk.graphmodel.FragmentManager;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.hanuna.gitalk.graphmodel.GraphModel;
+import org.hanuna.gitalk.graphmodel.impl.GraphModelImpl;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static junit.framework.Assert.assertEquals;
+import static org.hanuna.gitalk.graph.GraphStrUtils.toStr;
+import static org.hanuna.gitalk.graph.GraphTestUtils.getCommitNode;
+
+/**
+ * @author erokhins
+ */
+public class GraphModelTest {
+
+    private GraphModel simpleGraph;
+    private GraphModel middleGraph;
+    private GraphModel hardGraph;
+
+
+    @NotNull
+    public GraphModel buildGraphModel(@NotNull String inputGraph) {
+        MutableGraph graph = GraphTestUtils.getNewMutableGraph(inputGraph);
+        return new GraphModelImpl(graph, Collections.<Ref>emptyList());
+    }
+
+
+    /**
+     * a0
+     *  |
+     * a1
+     *  |
+     * a2
+     *  |
+     *  | a3
+     *  |  |
+     *  | a4
+     *  |  |
+     *  | a5
+     *  | /
+     *  a6
+     *  |
+     *  a7
+     *  |
+     *  a8
+     *  |
+     *  a9
+     */
+
+
+    @Before
+    public void initMiddleGraph() {
+        middleGraph = buildGraphModel(
+                "a0|-a1\n" +
+                        "a1|-a2\n" +
+                        "a2|-a6\n" +
+                        "a3|-a4\n" +
+                        "a4|-a5\n" +
+                        "a5|-a6\n" +
+                        "a6|-a7\n" +
+                        "a7|-a8\n" +
+                        "a8|-a9\n" +
+                        "a9|-"
+        );
+        assertEquals("init graph",
+                toStr(middleGraph.getGraph()),
+
+                "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a1|-a0:a1:USUAL:a0|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+                "a2|-a1:a2:USUAL:a0|-a2:a6:USUAL:a0|-COMMIT_NODE|-a0|-2\n" +
+                "a3|-|-a3:a4:USUAL:a3|-COMMIT_NODE|-a3|-3\n" +
+                "a4|-a3:a4:USUAL:a3|-a4:a5:USUAL:a3|-COMMIT_NODE|-a3|-4\n" +
+                "a5|-a4:a5:USUAL:a3|-a5:a6:USUAL:a3|-COMMIT_NODE|-a3|-5\n" +
+                "a6|-a2:a6:USUAL:a0 a5:a6:USUAL:a3|-a6:a7:USUAL:a0|-COMMIT_NODE|-a0|-6\n" +
+                "a7|-a6:a7:USUAL:a0|-a7:a8:USUAL:a0|-COMMIT_NODE|-a0|-7\n" +
+                "a8|-a7:a8:USUAL:a0|-a8:a9:USUAL:a0|-COMMIT_NODE|-a0|-8\n" +
+                "a9|-a8:a9:USUAL:a0|-|-COMMIT_NODE|-a0|-9"
+        );
+    }
+
+
+
+
+
+
+    /**
+     * a0
+     *  |
+     * a1
+     *  |
+     * a2
+     *  |\
+     *  | | a3
+     *  | |  |
+     *  | | a4
+     *  | |  |
+     *  | | a5
+     *  | \ /
+     *  | a6
+     *  |  |
+     *  | a7
+     *  |  |
+     *  | a8
+     *  | /
+     *  *  a9
+     *  | /
+     *  *  a10
+     *  |   |
+     * a11  |
+     *  |   |
+     * a12  |
+     *  |   |
+     *  |  a13
+     *  |   |
+     *  |  a14
+     *   \ /
+     *   a15
+     *    |
+     *   a16
+     *    |
+     *   a17
+     */
+
+
+    @Before
+    public void initHardGraph() {
+        hardGraph = buildGraphModel(
+                "a0|-a1\n" +
+                "a1|-a2\n" +
+                "a2|-a11 a6\n" +
+                "a3|-a4\n" +
+                "a4|-a5\n" +
+                "a5|-a6\n" +
+                "a6|-a7\n" +
+                "a7|-a8\n" +
+                "a8|-a11\n" +
+                "a9|-a11\n" +
+                "a10|-a13\n" +
+                "a11|-a12\n" +
+                "a12|-a15\n" +
+                "a13|-a14\n" +
+                "a14|-a15\n" +
+                "a15|-a16\n" +
+                "a16|-a17\n" +
+                "a17|-"
+        );
+        assertEquals("init graph",
+
+                "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a1|-a0:a1:USUAL:a0|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+                "a2|-a1:a2:USUAL:a0|-a2:a11:USUAL:a2#a11 a2:a6:USUAL:a2#a6|-COMMIT_NODE|-a0|-2\n" +
+                "a3|-|-a3:a4:USUAL:a3|-COMMIT_NODE|-a3|-3\n" +
+                "a4|-a3:a4:USUAL:a3|-a4:a5:USUAL:a3|-COMMIT_NODE|-a3|-4\n" +
+                "a5|-a4:a5:USUAL:a3|-a5:a6:USUAL:a3|-COMMIT_NODE|-a3|-5\n" +
+                "a6|-a2:a6:USUAL:a2#a6 a5:a6:USUAL:a3|-a6:a7:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-6\n" +
+                "a7|-a6:a7:USUAL:a2#a6|-a7:a8:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-7\n" +
+                "a8|-a7:a8:USUAL:a2#a6|-a8:a11:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-8\n" +
+                "a11|-a2:a11:USUAL:a2#a11 a8:a11:USUAL:a2#a6|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-9\n" +
+                "   a9|-|-a9:a11:USUAL:a9|-COMMIT_NODE|-a9|-9\n" +
+                "a10|-|-a10:a13:USUAL:a10|-COMMIT_NODE|-a10|-10\n" +
+                "   a11|-a11:a11:USUAL:a2#a11 a9:a11:USUAL:a9|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-10\n" +
+                "a11|-a11:a11:USUAL:a2#a11|-a11:a12:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-11\n" +
+                "a12|-a11:a12:USUAL:a2#a11|-a12:a15:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-12\n" +
+                "a13|-a10:a13:USUAL:a10|-a13:a14:USUAL:a10|-COMMIT_NODE|-a10|-13\n" +
+                "a14|-a13:a14:USUAL:a10|-a14:a15:USUAL:a10|-COMMIT_NODE|-a10|-14\n" +
+                "a15|-a12:a15:USUAL:a2#a11 a14:a15:USUAL:a10|-a15:a16:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-15\n" +
+                "a16|-a15:a16:USUAL:a2#a11|-a16:a17:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-16\n" +
+                "a17|-a16:a17:USUAL:a2#a11|-|-COMMIT_NODE|-a2#a11|-17",
+
+                toStr(hardGraph.getGraph())
+        );
+    }
+
+
+    private void setVisibleBranches(@NotNull GraphModel graphModel, @NotNull final  Set<String> startedNodes) {
+        graphModel.setVisibleBranchesNodes(new Function<Node, Boolean>() {
+            @NotNull
+            @Override
+            public Boolean fun(@NotNull Node key) {
+                return startedNodes.contains(key.getCommitHash().toStrHash());
+            }
+        });
+    }
+
+
+    private void runTestGraphBranchesVisibility(@NotNull GraphModel graphModel,
+                                               @NotNull Set<String> startedNodes, @NotNull String outStr) {
+        setVisibleBranches(graphModel, startedNodes);
+        assertEquals(outStr, toStr(graphModel.getGraph()));
+    }
+
+    @Test
+    public void middle1() {
+        Set<String> startNodes = new HashSet<String>();
+        startNodes.add("a0");
+
+        runTestGraphBranchesVisibility(
+                middleGraph,
+
+                startNodes,
+
+                "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+                        "a1|-a0:a1:USUAL:a0|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+                        "a2|-a1:a2:USUAL:a0|-a2:a6:USUAL:a0|-COMMIT_NODE|-a0|-2\n" +
+                        "a6|-a2:a6:USUAL:a0|-a6:a7:USUAL:a0|-COMMIT_NODE|-a0|-3\n" +
+                        "a7|-a6:a7:USUAL:a0|-a7:a8:USUAL:a0|-COMMIT_NODE|-a0|-4\n" +
+                        "a8|-a7:a8:USUAL:a0|-a8:a9:USUAL:a0|-COMMIT_NODE|-a0|-5\n" +
+                        "a9|-a8:a9:USUAL:a0|-|-COMMIT_NODE|-a0|-6");
+
+    }
+
+    @Test
+    public void middle2() {
+        Set<String> startNodes = new HashSet<String>();
+        startNodes.add("a1");
+
+        runTestGraphBranchesVisibility(
+                middleGraph,
+
+                startNodes,
+
+                "a1|-|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a2|-a1:a2:USUAL:a0|-a2:a6:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+                "a6|-a2:a6:USUAL:a0|-a6:a7:USUAL:a0|-COMMIT_NODE|-a0|-2\n" +
+                "a7|-a6:a7:USUAL:a0|-a7:a8:USUAL:a0|-COMMIT_NODE|-a0|-3\n" +
+                "a8|-a7:a8:USUAL:a0|-a8:a9:USUAL:a0|-COMMIT_NODE|-a0|-4\n" +
+                "a9|-a8:a9:USUAL:a0|-|-COMMIT_NODE|-a0|-5"
+        );
+
+    }
+
+    @Test
+    public void middleTwoBranch() {
+        Set<String> startNodes = new HashSet<String>();
+        startNodes.add("a1");
+        startNodes.add("a5");
+
+        runTestGraphBranchesVisibility(
+                middleGraph,
+
+                startNodes,
+               "a1|-|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+               "a2|-a1:a2:USUAL:a0|-a2:a6:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+               "a5|-|-a5:a6:USUAL:a3|-COMMIT_NODE|-a3|-2\n" +
+               "a6|-a2:a6:USUAL:a0 a5:a6:USUAL:a3|-a6:a7:USUAL:a0|-COMMIT_NODE|-a0|-3\n" +
+               "a7|-a6:a7:USUAL:a0|-a7:a8:USUAL:a0|-COMMIT_NODE|-a0|-4\n" +
+               "a8|-a7:a8:USUAL:a0|-a8:a9:USUAL:a0|-COMMIT_NODE|-a0|-5\n" +
+               "a9|-a8:a9:USUAL:a0|-|-COMMIT_NODE|-a0|-6"
+        );
+
+    }
+
+
+
+    //////////////Hard
+
+
+
+    @Test
+    public void hard1() {
+        Set<String> startNodes = new HashSet<String>();
+        startNodes.add("a0");
+
+        runTestGraphBranchesVisibility(
+                hardGraph,
+
+                startNodes,
+
+                "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a1|-a0:a1:USUAL:a0|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+                "a2|-a1:a2:USUAL:a0|-a2:a11:USUAL:a2#a11 a2:a6:USUAL:a2#a6|-COMMIT_NODE|-a0|-2\n" +
+                "a6|-a2:a6:USUAL:a2#a6|-a6:a7:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-3\n" +
+                "a7|-a6:a7:USUAL:a2#a6|-a7:a8:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-4\n" +
+                "a8|-a7:a8:USUAL:a2#a6|-a8:a11:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-5\n" +
+                "a11|-a2:a11:USUAL:a2#a11 a8:a11:USUAL:a2#a6|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-6\n" +
+                "a11|-a11:a11:USUAL:a2#a11|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-7\n" +
+                "a11|-a11:a11:USUAL:a2#a11|-a11:a12:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-8\n" +
+                "a12|-a11:a12:USUAL:a2#a11|-a12:a15:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-9\n" +
+                "a15|-a12:a15:USUAL:a2#a11|-a15:a16:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-10\n" +
+                "a16|-a15:a16:USUAL:a2#a11|-a16:a17:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-11\n" +
+                "a17|-a16:a17:USUAL:a2#a11|-|-COMMIT_NODE|-a2#a11|-12"
+        );
+
+    }
+
+    @Test
+    public void hardSeveralBranches() {
+        Set<String> startNodes = new HashSet<String>();
+        startNodes.add("a3");
+        startNodes.add("a10");
+
+        runTestGraphBranchesVisibility(
+                hardGraph,
+
+                startNodes,
+
+                "a3|-|-a3:a4:USUAL:a3|-COMMIT_NODE|-a3|-0\n" +
+                "a4|-a3:a4:USUAL:a3|-a4:a5:USUAL:a3|-COMMIT_NODE|-a3|-1\n" +
+                "a5|-a4:a5:USUAL:a3|-a5:a6:USUAL:a3|-COMMIT_NODE|-a3|-2\n" +
+                "a6|-a5:a6:USUAL:a3|-a6:a7:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-3\n" +
+                "a7|-a6:a7:USUAL:a2#a6|-a7:a8:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-4\n" +
+                "a8|-a7:a8:USUAL:a2#a6|-a8:a11:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-5\n" +
+                "a11|-a8:a11:USUAL:a2#a6|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-6\n" +
+                "a10|-|-a10:a13:USUAL:a10|-COMMIT_NODE|-a10|-7\n" +
+                "   a11|-a11:a11:USUAL:a2#a11|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-7\n" +
+                "a11|-a11:a11:USUAL:a2#a11|-a11:a12:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-8\n" +
+                "a12|-a11:a12:USUAL:a2#a11|-a12:a15:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-9\n" +
+                "a13|-a10:a13:USUAL:a10|-a13:a14:USUAL:a10|-COMMIT_NODE|-a10|-10\n" +
+                "a14|-a13:a14:USUAL:a10|-a14:a15:USUAL:a10|-COMMIT_NODE|-a10|-11\n" +
+                "a15|-a12:a15:USUAL:a2#a11 a14:a15:USUAL:a10|-a15:a16:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-12\n" +
+                "a16|-a15:a16:USUAL:a2#a11|-a16:a17:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-13\n" +
+                "a17|-a16:a17:USUAL:a2#a11|-|-COMMIT_NODE|-a2#a11|-14"
+        );
+
+    }
+
+    @Test
+    public void hardMiddleSelect() {
+        Set<String> startNodes = new HashSet<String>();
+        startNodes.add("a9");
+
+        runTestGraphBranchesVisibility(
+                hardGraph,
+
+                startNodes,
+
+                "a9|-|-a9:a11:USUAL:a9|-COMMIT_NODE|-a9|-0\n" +
+                "a11|-a9:a11:USUAL:a9|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-1\n" +
+                "a11|-a11:a11:USUAL:a2#a11|-a11:a12:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-2\n" +
+                "a12|-a11:a12:USUAL:a2#a11|-a12:a15:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-3\n" +
+                "a15|-a12:a15:USUAL:a2#a11|-a15:a16:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-4\n" +
+                "a16|-a15:a16:USUAL:a2#a11|-a16:a17:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-5\n" +
+                "a17|-a16:a17:USUAL:a2#a11|-|-COMMIT_NODE|-a2#a11|-6"
+        );
+
+    }
+
+
+
+    ///------------------hideLongEdges
+
+    @Nullable
+    private GraphFragment getRelateFragment(@NotNull GraphModel graphModel, int rowIndex) {
+        return graphModel.getFragmentManager().relateFragment(getCommitNode(graphModel.getGraph(), rowIndex));
+    }
+
+    @Test
+    public void middleSimpleHide() {
+        Set<String> startNodes = new HashSet<String>();
+
+        FragmentManager fragmentManager = middleGraph.getFragmentManager();
+        fragmentManager.showAll();
+
+        startNodes.add("a0");
+        setVisibleBranches(middleGraph, startNodes);
+        fragmentManager.changeVisibility(getRelateFragment(middleGraph, 0));
+
+        assertEquals("hide long branch",
+
+                "a0|-|-a0:a9:HIDE_FRAGMENT:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a9|-a0:a9:HIDE_FRAGMENT:a0|-|-COMMIT_NODE|-a0|-1",
+
+                toStr(middleGraph.getGraph()));
+
+        startNodes.add("a3");
+        runTestGraphBranchesVisibility(
+                middleGraph,
+                startNodes,
+                "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a1|-a0:a1:USUAL:a0|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+                "a2|-a1:a2:USUAL:a0|-a2:a6:USUAL:a0|-COMMIT_NODE|-a0|-2\n" +
+                "a3|-|-a3:a4:USUAL:a3|-COMMIT_NODE|-a3|-3\n" +
+                "a4|-a3:a4:USUAL:a3|-a4:a5:USUAL:a3|-COMMIT_NODE|-a3|-4\n" +
+                "a5|-a4:a5:USUAL:a3|-a5:a6:USUAL:a3|-COMMIT_NODE|-a3|-5\n" +
+                "a6|-a2:a6:USUAL:a0 a5:a6:USUAL:a3|-a6:a7:USUAL:a0|-COMMIT_NODE|-a0|-6\n" +
+                "a7|-a6:a7:USUAL:a0|-a7:a8:USUAL:a0|-COMMIT_NODE|-a0|-7\n" +
+                "a8|-a7:a8:USUAL:a0|-a8:a9:USUAL:a0|-COMMIT_NODE|-a0|-8\n" +
+                "a9|-a8:a9:USUAL:a0|-|-COMMIT_NODE|-a0|-9"
+        );
+
+        fragmentManager.showAll();
+    }
+
+
+
+    @Test
+    public void middleHardHide() {
+        Set<String> startNodes = new HashSet<String>();
+
+        FragmentManager fragmentManager = middleGraph.getFragmentManager();
+        fragmentManager.showAll();
+
+        startNodes.add("a0");
+        setVisibleBranches(middleGraph, startNodes);
+        fragmentManager.changeVisibility(getRelateFragment(middleGraph, 0));
+
+        assertEquals("hide long branch",
+
+                "a0|-|-a0:a9:HIDE_FRAGMENT:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a9|-a0:a9:HIDE_FRAGMENT:a0|-|-COMMIT_NODE|-a0|-1",
+
+                toStr(middleGraph.getGraph()));
+
+        startNodes.clear();
+        startNodes.add("a3");
+
+
+        runTestGraphBranchesVisibility(
+                middleGraph,
+                startNodes,
+                "a3|-|-a3:a4:USUAL:a3|-COMMIT_NODE|-a3|-0\n" +
+                "a4|-a3:a4:USUAL:a3|-a4:a5:USUAL:a3|-COMMIT_NODE|-a3|-1\n" +
+                "a5|-a4:a5:USUAL:a3|-a5:a6:USUAL:a3|-COMMIT_NODE|-a3|-2\n" +
+                "a6|-a5:a6:USUAL:a3|-a6:a7:USUAL:a0|-COMMIT_NODE|-a0|-3\n" +
+                "a7|-a6:a7:USUAL:a0|-a7:a8:USUAL:a0|-COMMIT_NODE|-a0|-4\n" +
+                "a8|-a7:a8:USUAL:a0|-a8:a9:USUAL:a0|-COMMIT_NODE|-a0|-5\n" +
+                "a9|-a8:a9:USUAL:a0|-|-COMMIT_NODE|-a0|-6"
+        );
+
+        fragmentManager.changeVisibility(getRelateFragment(middleGraph, 0));
+
+        startNodes.add("a0");
+        runTestGraphBranchesVisibility(
+                middleGraph,
+                startNodes,
+                "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a1|-a0:a1:USUAL:a0|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+                "a2|-a1:a2:USUAL:a0|-a2:a6:USUAL:a0|-COMMIT_NODE|-a0|-2\n" +
+                "a3|-|-a3:a4:USUAL:a3|-COMMIT_NODE|-a3|-3\n" +
+                "a4|-a3:a4:USUAL:a3|-a4:a5:USUAL:a3|-COMMIT_NODE|-a3|-4\n" +
+                "a5|-a4:a5:USUAL:a3|-a5:a6:USUAL:a3|-COMMIT_NODE|-a3|-5\n" +
+                "a6|-a2:a6:USUAL:a0 a5:a6:USUAL:a3|-a6:a7:USUAL:a0|-COMMIT_NODE|-a0|-6\n" +
+                "a7|-a6:a7:USUAL:a0|-a7:a8:USUAL:a0|-COMMIT_NODE|-a0|-7\n" +
+                "a8|-a7:a8:USUAL:a0|-a8:a9:USUAL:a0|-COMMIT_NODE|-a0|-8\n" +
+                "a9|-a8:a9:USUAL:a0|-|-COMMIT_NODE|-a0|-9"
+        );
+
+
+        fragmentManager.showAll();
+    }
+
+    @Test
+    public void hardHardHideTest() {
+        Set<String> startNodes = new HashSet<String>();
+        FragmentManager fragmentManager = hardGraph.getFragmentManager();
+        fragmentManager.showAll();
+
+        startNodes.add("a3");
+        startNodes.add("a9");
+        startNodes.add("a10");
+        setVisibleBranches(hardGraph, startNodes);
+
+        fragmentManager.changeVisibility(getRelateFragment(hardGraph, 0));
+
+        assertEquals("first hide",
+
+                "a3|-|-a3:a8:HIDE_FRAGMENT:a3|-COMMIT_NODE|-a3|-0\n" +
+                "a8|-a3:a8:HIDE_FRAGMENT:a3|-a8:a11:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-1\n" +
+                "a11|-a8:a11:USUAL:a2#a6|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-2\n" +
+                "   a9|-|-a9:a11:USUAL:a9|-COMMIT_NODE|-a9|-2\n" +
+                "a10|-|-a10:a13:USUAL:a10|-COMMIT_NODE|-a10|-3\n" +
+                "   a11|-a11:a11:USUAL:a2#a11 a9:a11:USUAL:a9|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-3\n" +
+                "a11|-a11:a11:USUAL:a2#a11|-a11:a12:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-4\n" +
+                "a12|-a11:a12:USUAL:a2#a11|-a12:a15:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-5\n" +
+                "a13|-a10:a13:USUAL:a10|-a13:a14:USUAL:a10|-COMMIT_NODE|-a10|-6\n" +
+                "a14|-a13:a14:USUAL:a10|-a14:a15:USUAL:a10|-COMMIT_NODE|-a10|-7\n" +
+                "a15|-a12:a15:USUAL:a2#a11 a14:a15:USUAL:a10|-a15:a16:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-8\n" +
+                "a16|-a15:a16:USUAL:a2#a11|-a16:a17:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-9\n" +
+                "a17|-a16:a17:USUAL:a2#a11|-|-COMMIT_NODE|-a2#a11|-10",
+
+                toStr(hardGraph.getGraph())
+        );
+
+        startNodes.remove("a9");
+        setVisibleBranches(hardGraph, startNodes);
+
+        fragmentManager.changeVisibility(getRelateFragment(hardGraph, 4));
+        assertEquals("second hide",
+
+                "a3|-|-a3:a12:HIDE_FRAGMENT:a3|-COMMIT_NODE|-a3|-0\n" +
+                "a10|-|-a10:a13:USUAL:a10|-COMMIT_NODE|-a10|-1\n" +
+                "a12|-a3:a12:HIDE_FRAGMENT:a3|-a12:a15:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-2\n" +
+                "a13|-a10:a13:USUAL:a10|-a13:a14:USUAL:a10|-COMMIT_NODE|-a10|-3\n" +
+                "a14|-a13:a14:USUAL:a10|-a14:a15:USUAL:a10|-COMMIT_NODE|-a10|-4\n" +
+                "a15|-a12:a15:USUAL:a2#a11 a14:a15:USUAL:a10|-a15:a16:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-5\n" +
+                "a16|-a15:a16:USUAL:a2#a11|-a16:a17:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-6\n" +
+                "a17|-a16:a17:USUAL:a2#a11|-|-COMMIT_NODE|-a2#a11|-7",
+
+                toStr(hardGraph.getGraph())
+        );
+
+        startNodes.remove("a10");
+        setVisibleBranches(hardGraph, startNodes);
+        fragmentManager.changeVisibility(getRelateFragment(hardGraph, 2));
+        assertEquals("last hide",
+
+                "a3|-|-a3:a17:HIDE_FRAGMENT:a3|-COMMIT_NODE|-a3|-0\n" +
+                "a17|-a3:a17:HIDE_FRAGMENT:a3|-|-COMMIT_NODE|-a2#a11|-1",
+
+                toStr(hardGraph.getGraph())
+        );
+
+
+        startNodes.clear();
+        startNodes.add("a0");
+        setVisibleBranches(hardGraph, startNodes);
+
+        assertEquals("show a0",
+
+                "a0|-|-a0:a1:USUAL:a0|-COMMIT_NODE|-a0|-0\n" +
+                "a1|-a0:a1:USUAL:a0|-a1:a2:USUAL:a0|-COMMIT_NODE|-a0|-1\n" +
+                "a2|-a1:a2:USUAL:a0|-a2:a11:USUAL:a2#a11 a2:a6:USUAL:a2#a6|-COMMIT_NODE|-a0|-2\n" +
+                "a6|-a2:a6:USUAL:a2#a6|-a6:a7:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-3\n" +
+                "a7|-a6:a7:USUAL:a2#a6|-a7:a8:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-4\n" +
+                "a8|-a7:a8:USUAL:a2#a6|-a8:a11:USUAL:a2#a6|-COMMIT_NODE|-a2#a6|-5\n" +
+                "a11|-a2:a11:USUAL:a2#a11 a8:a11:USUAL:a2#a6|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-6\n" +
+                "a11|-a11:a11:USUAL:a2#a11|-a11:a11:USUAL:a2#a11|-EDGE_NODE|-a2#a11|-7\n" +
+                "a11|-a11:a11:USUAL:a2#a11|-a11:a12:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-8\n" +
+                "a12|-a11:a12:USUAL:a2#a11|-a12:a15:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-9\n" +
+                "a15|-a12:a15:USUAL:a2#a11|-a15:a16:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-10\n" +
+                "a16|-a15:a16:USUAL:a2#a11|-a16:a17:USUAL:a2#a11|-COMMIT_NODE|-a2#a11|-11\n" +
+                "a17|-a16:a17:USUAL:a2#a11|-|-COMMIT_NODE|-a2#a11|-12",
+
+                toStr(hardGraph.getGraph())
+        );
+
+
+
+
+        fragmentManager.showAll();
+    }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/GraphModelUtils.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/GraphModelUtils.java
new file mode 100644 (file)
index 0000000..6b6af7b
--- /dev/null
@@ -0,0 +1,60 @@
+package org.hanuna.gitalk.graphmodel.fragment;
+
+import com.intellij.util.Function;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+import static org.hanuna.gitalk.graph.GraphTestUtils.getCommitNode;
+import static org.hanuna.gitalk.graph.GraphTestUtils.parseIntegers;
+
+/**
+ * @author erokhins
+ */
+public class GraphModelUtils {
+    public static String toShortStr(@NotNull Node node) {
+        return node.getCommitHash().toStrHash() + ":" + node.getRowIndex();
+    }
+
+
+    public static String toStr(@Nullable GraphFragment fragment) {
+        if (fragment == null) {
+            return "null";
+        }
+        StringBuilder s = new StringBuilder();
+        s.append(toShortStr(fragment.getUpNode())).append("|-");
+
+        List<String> intermediateNodeStr = new ArrayList<String>();
+        for (Node intermediateNode : fragment.getIntermediateNodes()) {
+            intermediateNodeStr.add(toShortStr(intermediateNode));
+        }
+        Collections.sort(intermediateNodeStr);
+        for (int i = 0; i < intermediateNodeStr.size(); i++) {
+            if (i > 0) {
+                s.append(" ");
+            }
+            s.append(intermediateNodeStr.get(i));
+        }
+        s.append("|-").append(toShortStr(fragment.getDownNode()));
+        return s.toString();
+    }
+
+    public static Function<Node, Boolean> parseUnhiddenNodes(Graph graph, String unhiddenNodeRows) {
+        Set<Integer> unhiddenNodesRowIndex = parseIntegers(unhiddenNodeRows);
+        final Set<Node> unhiddenNodes = new HashSet<Node>();
+        for (Integer i : unhiddenNodesRowIndex) {
+            unhiddenNodes.add(getCommitNode(graph, i));
+        }
+        return new Function<Node, Boolean>() {
+            @NotNull
+            @Override
+            public Boolean fun(@NotNull Node key) {
+                return unhiddenNodes.contains(key);
+            }
+        };
+    }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/ShortFragmentTest.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/graphmodel/fragment/ShortFragmentTest.java
new file mode 100644 (file)
index 0000000..ab50a53
--- /dev/null
@@ -0,0 +1,315 @@
+package org.hanuna.gitalk.graphmodel.fragment;
+
+import junit.framework.Assert;
+import org.hanuna.gitalk.graph.GraphTestUtils;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.mutable.MutableGraph;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import static org.hanuna.gitalk.graph.GraphTestUtils.getCommitNode;
+import static org.hanuna.gitalk.graphmodel.fragment.GraphModelUtils.parseUnhiddenNodes;
+import static org.hanuna.gitalk.graphmodel.fragment.GraphModelUtils.toStr;
+
+/**
+ * @author erokhins
+ */
+public class ShortFragmentTest {
+
+    public void runTest(@NotNull String inputGraph, int rowIndex, String fragmentStr, final String unhiddenNodeRows, boolean down) {
+        MutableGraph graph = GraphTestUtils.getNewMutableGraph(inputGraph);
+        ShortFragmentGenerator shortFragmentGenerator = new ShortFragmentGenerator(graph);
+        shortFragmentGenerator.setUnconcealedNodeFunction(parseUnhiddenNodes(graph, unhiddenNodeRows));
+
+        Node commitNode = getCommitNode(graph, rowIndex);
+        GraphFragment fragment;
+        if (down) {
+            fragment = shortFragmentGenerator.getDownShortFragment(commitNode);
+        } else {
+            fragment = shortFragmentGenerator.getUpShortFragment(commitNode);
+        }
+        Assert.assertEquals(fragmentStr, toStr(fragment));
+    }
+
+    public void runTest(@NotNull String inputGraph, int rowIndex, String fragmentStr, boolean down) {
+        runTest(inputGraph, rowIndex, fragmentStr, "", down);
+    }
+
+
+
+    @Test
+    public void simpleDownTest() {
+        runTest(
+                "a0|-a1\n" +
+                        "a1|-a2\n" +
+                        "a2|-",
+                0,
+
+                "a0:0|-|-a1:1",
+                true
+        );
+    }
+
+    @Test
+    public void simpleUpTest() {
+        runTest(
+                "a0|-a1\n" +
+                "a1|-a2\n"  +
+                "a2|-",
+                1,
+
+                "a0:0|-|-a1:1",
+                false
+        );
+    }
+
+    @Test
+    public void simpleNullDown() {
+        runTest(
+                "a0|-a1\n" +
+                        "a1|-a2\n" +
+                        "a2|-",
+                2,
+                "null",
+                true
+        );
+    }
+
+    @Test
+    public void simpleNullUp() {
+        runTest(
+                "a0|-a1\n" +
+                "a1|-a2\n"  +
+                "a2|-",
+                0,
+                "null",
+                false
+        );
+    }
+
+
+
+    @Test
+    public void severalChildrenDown() {
+        runTest(
+                "a0|-a1 a2\n" +
+                        "a1|-a2\n" +
+                        "a2|-",
+                0,
+                "a0:0|-a1:1|-a2:2",
+                true
+        );
+    }
+
+
+    @Test
+    public void severalChildrenUp() {
+        runTest(
+                "a0|-a1 a2\n" +
+                "a1|-a2\n"  +
+                "a2|-",
+                2,
+                "a0:0|-a1:1|-a2:2",
+                false
+        );
+    }
+
+
+
+    @Test
+    public void badEndNodeDown() {
+        runTest(
+                "a0|-a1 a3\n" +
+                        "a1|-a3\n" +
+                        "a2|-a3\n" +
+                        "a3|-",
+                0,
+                "null",
+                true
+        );
+    }
+
+    @Test
+    public void badEndNodeUp() {
+        runTest(
+                "a0|-a2 a3 a1\n" +
+                "a1|-\n"  +
+                "a2|-a3\n" +
+                "a3|-",
+                3,
+                "null",
+                false
+        );
+    }
+
+
+
+    @Test
+    public void badIntermediateNodeDown() {
+        runTest(
+                "a0|-a2 a3\n" +
+                        "a1|-a2\n" +
+                        "a2|-a3\n" +
+                        "a3|-",
+                0,
+                "null",
+                true
+        );
+    }
+
+    @Test
+    public void badIntermediateNodeUp() {
+        runTest(
+                "a0|-a1 a3\n" +
+                "a1|-a2 a3\n"  +
+                "a2|-\n" +
+                "a3|-",
+                3,
+                "null",
+                false
+        );
+    }
+
+
+    @Test
+    public void longEndNodeDown() {
+        runTest(
+                "a0|-a1 a2\n" +
+                        "a1|-a2\n" +
+                        "a2|-a3\n" +
+                        "a3|-",
+                0,
+                "a0:0|-a1:1|-a2:2",
+                true
+        );
+    }
+
+    @Test
+    public void longEndNodeUp() {
+        runTest(
+                "a0|-a1\n" +
+                "a1|-a2 a3\n"  +
+                "a2|-a3\n" +
+                "a3|-",
+                3,
+                "a1:1|-a2:2|-a3:3",
+                false
+        );
+    }
+
+
+    @Test
+    public void edgeNodesDown() {
+        runTest(
+                "a0|-a3 a1\n" +
+                        "a1|-a2 a3\n" +
+                        "a2|-a3\n" +
+                        "a3|-",
+                0,
+                "a0:0|-a1:1 a2:2 a3:2|-a3:3",
+                true
+        );
+    }
+
+    @Test
+    public void edgeNodesUp() {
+        runTest(
+                "a0|-a3 a1\n" +
+                "a1|-a2 a3\n"  +
+                "a2|-a3\n" +
+                "a3|-",
+                3,
+                "a0:0|-a1:1 a2:2 a3:2|-a3:3",
+                false
+        );
+    }
+
+    @Test
+    public void unhiddenMiddleTestDown() {
+        runTest(
+                "a0|-a3 a1\n" +
+                        "a1|-a2 a3\n" +
+                        "a2|-a3\n" +
+                        "a3|-",
+                0,
+                "null",
+                "1",
+                true
+        );
+    }
+
+
+    @Test
+    public void unhiddenMiddleTestUp() {
+        runTest(
+                "a0|-a3 a1\n" +
+                "a1|-a2 a3\n" +
+                "a2|-a3\n" +
+                "a3|-",
+                3,
+                "null",
+                "1",
+                false
+        );
+    }
+
+    @Test
+    public void unhiddenEndTestDown() {
+        runTest(
+                "a0|-a3 a1\n" +
+                        "a1|-a2 a3\n" +
+                        "a2|-a3\n" +
+                        "a3|-",
+                0,
+                "a0:0|-a1:1 a2:2 a3:2|-a3:3",
+                "0 3",
+                true
+        );
+    }
+
+
+    @Test
+    public void unhiddenEndTestUp() {
+        runTest(
+                "a0|-a3 a1\n" +
+                "a1|-a2 a3\n" +
+                "a2|-a3\n" +
+                "a3|-",
+                3,
+                "a0:0|-a1:1 a2:2 a3:2|-a3:3",
+                "0 3",
+                false
+        );
+    }
+
+
+    @Test
+    public void doubleLineEndTestDown() {
+        runTest(
+                "a0|-a2 a1\n" +
+                "a1|-\n" +
+                "a2|-",
+                0,
+                "a0:0|-a1:1|-a2:2",
+                true
+        );
+    }
+
+    @Test
+    public void doubleLineEndTestUp() {
+        runTest(
+                "a0|-a2\n" +
+                "a1|-a2\n" +
+                "a2|-",
+                2,
+                "a0:0|-a1:1|-a2:2",
+                false
+        );
+    }
+
+
+
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/CommitDataParserTest.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/CommitDataParserTest.java
new file mode 100644 (file)
index 0000000..dd48ac6
--- /dev/null
@@ -0,0 +1,67 @@
+package org.hanuna.gitalk.log.parser;
+
+import com.intellij.vcs.log.VcsCommit;
+import junit.framework.Assert;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+
+/**
+ * @author erokhins
+ */
+public class CommitDataParserTest {
+
+  private static String toStr(@NotNull VcsCommit commitData) {
+    StringBuilder s = new StringBuilder();
+    s.append(commitData.getHash()).append("|-");
+    s.append(commitData.getAuthorName()).append("|-");
+    s.append(commitData.getAuthorTime()).append("|-");
+    s.append(commitData.getSubject());
+    return s.toString();
+  }
+
+  private void runTest(@NotNull String inputStr) {
+    VcsCommit commitData = CommitParser.parseCommitData(inputStr);
+    assertEquals(inputStr, toStr(commitData));
+  }
+
+  @Test
+  public void simple1() {
+    runTest("af56|-author|-1435|-message");
+  }
+
+  @Test
+  public void emptyMessage() {
+    runTest("12|-author|-1435|-");
+  }
+
+  @Test
+  public void longAuthor() {
+    runTest("af56|-author  skdhfb  j 2353246|-1435|-message");
+  }
+
+  @Test
+  public void strangeMessage() {
+    runTest("af56|-author |-1435|-m|-|-es dfsage");
+  }
+
+  @Test
+  public void bigTimestamp() {
+    runTest("af56|-author |-143523623|-m|-|-es dfsage");
+  }
+
+  @Test
+  public void emptyAuthor() {
+    runTest("af56|-|-143523623|-");
+  }
+
+  @Test
+  public void emptyTimestamp() {
+    VcsCommit commitData = CommitParser.parseCommitData("af56|-author |-|-message");
+    Assert.assertEquals("author ", commitData.getAuthorName());
+    Assert.assertEquals(0, commitData.getAuthorTime());
+    Assert.assertEquals("message", commitData.getSubject());
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/CommitParser.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/CommitParser.java
new file mode 100644 (file)
index 0000000..d0f57d5
--- /dev/null
@@ -0,0 +1,108 @@
+package org.hanuna.gitalk.log.parser;
+
+import com.intellij.vcs.log.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class CommitParser {
+
+  private static int nextSeparatorIndex(@NotNull String line, int startIndex) {
+    int nextIndex = line.indexOf("|-", startIndex);
+    if (nextIndex == -1) {
+      throw new IllegalArgumentException("not found separator \"|-\", with startIndex=" + startIndex +
+                                         ", in line: " + line);
+    }
+    return nextIndex;
+  }
+
+  /**
+   * @param line input format:
+   *             ab123|-adada 193 352
+   *             123|-             // no parent
+   */
+  @NotNull
+  public static CommitParents parseCommitParents(@NotNull String line) {
+    int separatorIndex = nextSeparatorIndex(line, 0);
+    String commitHashStr = line.substring(0, separatorIndex);
+    Hash commitHash = Hash.build(commitHashStr);
+
+    String parentHashStr = line.substring(separatorIndex + 2, line.length());
+    String[] parentsHashes = parentHashStr.split("\\s");
+    List<Hash> hashes = new ArrayList<Hash>(parentsHashes.length);
+    for (String aParentsStr : parentsHashes) {
+      if (aParentsStr.length() > 0) {
+        hashes.add(Hash.build(aParentsStr));
+      }
+    }
+    return new SimpleCommitParents(commitHash, hashes);
+  }
+
+  /**
+   * @param line 1231423|-adada|-193 adf45
+   *             timestamp|-hash commit|-parent hashes
+   */
+  @NotNull
+  public static TimestampCommitParents parseTimestampParentHashes(@NotNull String line) {
+    int firstSeparatorIndex = nextSeparatorIndex(line, 0);
+    String timestampStr = line.substring(0, firstSeparatorIndex);
+    long timestamp;
+    try {
+      if (timestampStr.isEmpty()) {
+        timestamp = 0;
+      }
+      else {
+        timestamp = Long.parseLong(timestampStr);
+      }
+    }
+    catch (NumberFormatException e) {
+      throw new IllegalArgumentException("bad timestamp in line: " + line);
+    }
+    CommitParents commitParents = parseCommitParents(line.substring(firstSeparatorIndex + 2));
+
+    return new TimestampCommitParents(commitParents, timestamp);
+  }
+
+  /**
+   * @param line input format
+   *             hash|-author name|-123124|-commit message
+   */
+  @NotNull
+  public static VcsCommit parseCommitData(@NotNull String line) {
+    int prevIndex = 0;
+    int nextIndex = nextSeparatorIndex(line, 0);
+    final String hashStr = line.substring(0, nextIndex);
+
+    prevIndex = nextIndex;
+    nextIndex = nextSeparatorIndex(line, prevIndex + 1);
+    final String authorName = line.substring(prevIndex + 2, nextIndex);
+
+    prevIndex = nextIndex;
+    nextIndex = nextSeparatorIndex(line, prevIndex + 1);
+
+    String timestampStr = line.substring(prevIndex + 2, nextIndex);
+    final long timestamp;
+    try {
+      if (timestampStr.isEmpty()) {
+        timestamp = 0;
+      }
+      else {
+        timestamp = Long.parseLong(timestampStr);
+      }
+    }
+    catch (NumberFormatException e) {
+      throw new IllegalArgumentException("bad timestamp format: " + timestampStr + " in this Str: " + line);
+    }
+
+    final String commitMessage = line.substring(nextIndex + 2);
+
+    return new VcsCommitImpl(Hash.build(hashStr), Collections.<Hash>emptyList(), commitMessage, authorName ,timestamp);
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/CommitParserTest.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/CommitParserTest.java
new file mode 100644 (file)
index 0000000..059d9a2
--- /dev/null
@@ -0,0 +1,53 @@
+package org.hanuna.gitalk.log.parser;
+
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.CommitParents;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+
+/**
+ * @author erokhins
+ */
+public class CommitParserTest {
+  private String toStr(CommitParents commitParentHashes) {
+    StringBuilder s = new StringBuilder();
+    s.append(commitParentHashes.getHash().toStrHash()).append("|-");
+    for (int i = 0; i < commitParentHashes.getParents().size(); i++) {
+      Hash hash = commitParentHashes.getParents().get(i);
+      if (i != 0) {
+        s.append(" ");
+      }
+      s.append(hash.toStrHash());
+    }
+    return s.toString();
+  }
+
+  private void runTest(String inputStr) {
+    CommitParents commitParents = CommitParser.parseCommitParents(inputStr);
+    assertEquals(inputStr, toStr(commitParents));
+  }
+
+  @Test
+  public void simple1() {
+    runTest("a312|-");
+  }
+
+  @Test
+  public void parent() {
+    runTest("a312|-23");
+  }
+
+  @Test
+  public void twoParent() {
+    runTest("a312|-23 a54");
+  }
+
+
+  @Test
+  public void moreParent() {
+    runTest("a312|-23 a54 abcdef34 034f 00af 00000");
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/SimpleCommitListParser.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/SimpleCommitListParser.java
new file mode 100644 (file)
index 0000000..73acca7
--- /dev/null
@@ -0,0 +1,41 @@
+package org.hanuna.gitalk.log.parser;
+
+import com.intellij.vcs.log.CommitParents;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class SimpleCommitListParser {
+  public static List<CommitParents> parseCommitList(@NotNull String input) {
+    SimpleCommitListParser parser = new SimpleCommitListParser(new StringReader(input));
+    try {
+      return parser.readAllCommits();
+    }
+    catch (IOException e) {
+      throw new IllegalStateException();
+    }
+  }
+
+  private final BufferedReader bufferedReader;
+
+  public SimpleCommitListParser(StringReader bufferedReader) {
+    this.bufferedReader = new BufferedReader(bufferedReader);
+  }
+
+  public List<CommitParents> readAllCommits() throws IOException {
+    String line;
+    List<CommitParents> commitParentses = new ArrayList<CommitParents>();
+    while ((line = bufferedReader.readLine()) != null) {
+      commitParentses.add(CommitParser.parseCommitParents(line));
+    }
+    return commitParentses;
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/TimestampCommitParents.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/TimestampCommitParents.java
new file mode 100644 (file)
index 0000000..8817d6c
--- /dev/null
@@ -0,0 +1,37 @@
+package org.hanuna.gitalk.log.parser;
+
+import com.intellij.vcs.log.Hash;
+import com.intellij.vcs.log.CommitParents;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class TimestampCommitParents implements CommitParents {
+  private final CommitParents commitParents;
+  private final long timestamp;
+
+  public TimestampCommitParents(CommitParents commitParents, long timestamp) {
+    this.commitParents = commitParents;
+    this.timestamp = timestamp;
+  }
+
+  public long getTimestamp() {
+    return timestamp;
+  }
+
+  @NotNull
+  @Override
+  public Hash getHash() {
+    return commitParents.getHash();
+  }
+
+  @NotNull
+  @Override
+  public List<Hash> getParents() {
+    return commitParents.getParents();
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/TimestampCommitParentsParserTest.java b/platform/dvcs/vcs-log/vcs-log-graph/test/org/hanuna/gitalk/log/parser/TimestampCommitParentsParserTest.java
new file mode 100644 (file)
index 0000000..578ff0a
--- /dev/null
@@ -0,0 +1,54 @@
+package org.hanuna.gitalk.log.parser;
+
+import com.intellij.vcs.log.Hash;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+
+/**
+ * @author erokhins
+ */
+public class TimestampCommitParentsParserTest {
+
+  private String toStr(TimestampCommitParents commitParents) {
+    StringBuilder s = new StringBuilder();
+    s.append(commitParents.getTimestamp()).append("|-");
+    s.append(commitParents.getHash().toStrHash()).append("|-");
+    for (int i = 0; i < commitParents.getParents().size(); i++) {
+      Hash hash = commitParents.getParents().get(i);
+      if (i != 0) {
+        s.append(" ");
+      }
+      s.append(hash.toStrHash());
+    }
+    return s.toString();
+  }
+
+
+  private void runTest(String inputStr) {
+    TimestampCommitParents commitParents = CommitParser.parseTimestampParentHashes(inputStr);
+    assertEquals(inputStr, toStr(commitParents));
+  }
+
+  @Test
+  public void simple() {
+    runTest("1|-af|-");
+  }
+
+  @Test
+  public void parents() {
+    runTest("12314|-af|-12 fd");
+  }
+
+
+  @Test
+  public void parent() {
+    runTest("12314|-af|-12");
+  }
+
+
+  @Test
+  public void longTest() {
+    runTest("123142412423412|-af|-12");
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-graph/vcs-log-graph.iml b/platform/dvcs/vcs-log/vcs-log-graph/vcs-log-graph.iml
new file mode 100644 (file)
index 0000000..03c9e8e
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/lib/annotations.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="vcs-log-common" />
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/lib/junit-4.10.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="vcs-log-api" />
+    <orderEntry type="module" module-name="util" />
+    <orderEntry type="module" module-name="vcs-api" scope="TEST" />
+  </component>
+</module>
+
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/CommitSelectController.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/CommitSelectController.java
new file mode 100644 (file)
index 0000000..e8faec5
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2013 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 org.hanuna.gitalk.printmodel;
+
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class CommitSelectController {
+  private final Set<Node> selectedNodes = new HashSet<Node>();
+
+  private Node dragAndDropNode = null;
+  private boolean above;
+
+
+  // node == null - unSelect
+  public void selectDragAndDropNode(@Nullable Node node, boolean above) {
+    dragAndDropNode = node;
+    this.above = above;
+  }
+
+  public Node getDragAndDropNode() {
+    return dragAndDropNode;
+  }
+
+  public boolean isAbove() {
+    return above;
+  }
+
+  public void select(Set<Node> nodes) {
+    selectedNodes.addAll(nodes);
+  }
+
+  public void deselectAll() {
+    selectedNodes.clear();
+  }
+
+  public boolean isSelected(@NotNull Node element) {
+    return selectedNodes.contains(element);
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/GraphPrintCell.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/GraphPrintCell.java
new file mode 100644 (file)
index 0000000..e46f823
--- /dev/null
@@ -0,0 +1,22 @@
+package org.hanuna.gitalk.printmodel;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public interface GraphPrintCell {
+  public int countCell();
+
+  @NotNull
+  public List<ShortEdge> getUpEdges();
+
+  @NotNull
+  public List<ShortEdge> getDownEdges();
+
+  @NotNull
+  public List<SpecialPrintElement> getSpecialPrintElements();
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/GraphPrintCellModel.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/GraphPrintCellModel.java
new file mode 100644 (file)
index 0000000..6caf9a0
--- /dev/null
@@ -0,0 +1,27 @@
+package org.hanuna.gitalk.printmodel;
+
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public interface GraphPrintCellModel {
+
+  boolean HIDE_LONG_EDGES_DEFAULT = true;
+
+  @NotNull
+  public GraphPrintCell getGraphPrintCell(final int rowIndex);
+
+  @NotNull
+  public SelectController getSelectController();
+
+  @NotNull
+  public CommitSelectController getCommitSelectController();
+
+  public void recalculate(@NotNull UpdateRequest updateRequest);
+
+  public void setLongEdgeVisibility(boolean visibility);
+
+  boolean areLongEdgesHidden();
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/SelectController.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/SelectController.java
new file mode 100644 (file)
index 0000000..c4cdf9f
--- /dev/null
@@ -0,0 +1,50 @@
+package org.hanuna.gitalk.printmodel;
+
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author erokhins
+ */
+public class SelectController {
+  private final Set<GraphElement> selectedElements = new HashSet<GraphElement>();
+
+  public void select(@Nullable GraphFragment fragment) {
+    deselectAll();
+    if (fragment == null) {
+      return;
+    }
+    selectedElements.add(fragment.getUpNode());
+    selectedElements.add(fragment.getDownNode());
+    for (Edge edge : fragment.getUpNode().getDownEdges()) {
+      selectedElements.add(edge);
+    }
+    selectedElements.addAll(fragment.getIntermediateNodes());
+    for (Node node : fragment.getIntermediateNodes()) {
+      for (Edge edge : node.getDownEdges()) {
+        selectedElements.add(edge);
+      }
+    }
+  }
+
+  public void select(Set<GraphElement> selectedElements) {
+    this.selectedElements.addAll(selectedElements);
+  }
+
+  public void deselectAll() {
+    selectedElements.clear();
+  }
+
+  public boolean isSelected(@NotNull GraphElement element) {
+    return selectedElements.contains(element);
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/ShortEdge.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/ShortEdge.java
new file mode 100644 (file)
index 0000000..d934ed9
--- /dev/null
@@ -0,0 +1,48 @@
+package org.hanuna.gitalk.printmodel;
+
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public class ShortEdge {
+  private final Edge edge;
+  private final int upPosition;
+  private final int downPosition;
+  private final boolean selected;
+  private final boolean marked;
+
+  public ShortEdge(@NotNull Edge edge, int upPosition, int downPosition, boolean selected, boolean marked) {
+    this.edge = edge;
+    this.upPosition = upPosition;
+    this.downPosition = downPosition;
+    this.selected = selected;
+    this.marked = marked;
+  }
+
+  @NotNull
+  public Edge getEdge() {
+    return edge;
+  }
+
+  public boolean isMarked() {
+    return marked;
+  }
+
+  public boolean isUsual() {
+    return edge.getType() == Edge.EdgeType.USUAL;
+  }
+
+  public int getUpPosition() {
+    return upPosition;
+  }
+
+  public boolean isSelected() {
+    return selected;
+  }
+
+  public int getDownPosition() {
+    return downPosition;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/SpecialPrintElement.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/SpecialPrintElement.java
new file mode 100644 (file)
index 0000000..f089d5b
--- /dev/null
@@ -0,0 +1,63 @@
+package org.hanuna.gitalk.printmodel;
+
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author erokhins
+ */
+public class SpecialPrintElement {
+  private final GraphElement graphElement;
+  private final int position;
+  private final Type type;
+  private final boolean selected;
+
+  public boolean isMarked() {
+    return marked;
+  }
+
+  private final boolean marked;
+  private final int dragAndDropSelect;  // 0 -nothing -1 -down 1 - up
+
+  public SpecialPrintElement(@NotNull GraphElement graphElement,
+                             int position,
+                             @NotNull Type type,
+                             boolean selected,
+                             boolean marked,
+                             int dragAndDropSelect) {
+    this.graphElement = graphElement;
+    this.position = position;
+    this.type = type;
+    this.selected = selected;
+    this.marked = marked;
+    this.dragAndDropSelect = dragAndDropSelect;
+  }
+
+  @NotNull
+  public GraphElement getGraphElement() {
+    return graphElement;
+  }
+
+  public int getDragAndDropSelect() {
+    return dragAndDropSelect;
+  }
+
+  public int getPosition() {
+    return position;
+  }
+
+  public boolean isSelected() {
+    return selected;
+  }
+
+  @NotNull
+  public Type getType() {
+    return type;
+  }
+
+  public static enum Type {
+    COMMIT_NODE,
+    UP_ARROW,
+    DOWN_ARROW
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/impl/GraphElementsVisibilityController.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/impl/GraphElementsVisibilityController.java
new file mode 100644 (file)
index 0000000..1ed65da
--- /dev/null
@@ -0,0 +1,97 @@
+package org.hanuna.gitalk.printmodel.impl;
+
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.printmodel.layout.LayoutModel;
+import org.hanuna.gitalk.printmodel.layout.LayoutRow;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hanuna.gitalk.printmodel.impl.GraphElementsVisibilityController.VisibilityType.*;
+
+/**
+ * @author erokhins
+ */
+class GraphElementsVisibilityController {
+  private static final int LONG_EDGE = 20;
+  private static final int EDGE_PART_SHOW = 3;
+
+
+  private final LayoutModel layoutModel;
+  private boolean hideLongEdge;
+
+  public GraphElementsVisibilityController(boolean hideLongEdge, LayoutModel layoutModel) {
+    this.hideLongEdge = hideLongEdge;
+    this.layoutModel = layoutModel;
+  }
+
+  public void setHideLongEdge(boolean hideLongEdge) {
+    this.hideLongEdge = hideLongEdge;
+  }
+
+  @NotNull
+  public VisibilityType visibilityTypeEdge(Edge edge, int rowIndex) {
+    if (!hideLongEdge) {
+      return USUAL;
+    }
+    int upRowIndex = edge.getUpNode().getRowIndex();
+    int downRowIndex = edge.getDownNode().getRowIndex();
+    if (downRowIndex - upRowIndex < LONG_EDGE) {
+      return USUAL;
+    }
+
+    final int upDelta = rowIndex - upRowIndex;
+    final int downDelta = downRowIndex - rowIndex;
+    if (upDelta < EDGE_PART_SHOW || downDelta < EDGE_PART_SHOW) {
+      return USUAL;
+    }
+    if (upDelta == EDGE_PART_SHOW) {
+      return LAST_VISIBLE;
+    }
+    if (downDelta == EDGE_PART_SHOW) {
+      return FIRST_VISIBLE;
+    }
+
+    return HIDE;
+  }
+
+  @NotNull
+  public List<GraphElement> visibleElements(int rowIndex) {
+    if (rowIndex < 0 || rowIndex >= layoutModel.getLayoutRows().size()) {
+      return Collections.emptyList();
+    }
+    LayoutRow cellRow = layoutModel.getLayoutRows().get(rowIndex);
+    List<GraphElement> cells = cellRow.getOrderedGraphElements();
+    if (!hideLongEdge) {
+      return cells;
+    }
+
+    List<GraphElement> visibleElements = new ArrayList<GraphElement>();
+    for (GraphElement cell : cells) {
+      if (cell.getNode() != null) {
+        visibleElements.add(cell);
+      }
+      else {
+        Edge edge = cell.getEdge();
+        if (edge == null) {
+          throw new IllegalStateException();
+        }
+        if (visibilityTypeEdge(edge, rowIndex) != HIDE) {
+          visibleElements.add(cell);
+        }
+      }
+    }
+
+    return Collections.unmodifiableList(visibleElements);
+  }
+
+  public static enum VisibilityType {
+    USUAL,
+    LAST_VISIBLE,
+    FIRST_VISIBLE,
+    HIDE
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/impl/GraphPrintCellModelImpl.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/impl/GraphPrintCellModelImpl.java
new file mode 100644 (file)
index 0000000..20ddba8
--- /dev/null
@@ -0,0 +1,86 @@
+package org.hanuna.gitalk.printmodel.impl;
+
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.printmodel.*;
+import org.hanuna.gitalk.printmodel.layout.LayoutModel;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class GraphPrintCellModelImpl implements GraphPrintCellModel {
+  private final LayoutModel layoutModel;
+  private final SelectController selectController;
+  private boolean hideLongEdges = HIDE_LONG_EDGES_DEFAULT;
+  private final CommitSelectController commitSelectController;
+
+  public GraphPrintCellModelImpl(Graph graph) {
+    this.layoutModel = new LayoutModel(graph);
+    this.selectController = new SelectController();
+    this.commitSelectController = new CommitSelectController();
+  }
+
+  private List<ShortEdge> getUpEdges(int rowIndex) {
+    PrePrintCellModel prevPreModel = new PrePrintCellModel(hideLongEdges, layoutModel, rowIndex - 1, selectController,
+                                                           commitSelectController);
+    return prevPreModel.downShortEdges();
+  }
+
+  public void recalculate(@NotNull UpdateRequest updateRequest) {
+    layoutModel.recalculate(updateRequest);
+  }
+
+  @Override
+  public void setLongEdgeVisibility(boolean visibility) {
+    hideLongEdges = !visibility;
+  }
+
+  @Override
+  public boolean areLongEdgesHidden() {
+    return hideLongEdges;
+  }
+
+  @NotNull
+  public SelectController getSelectController() {
+    return selectController;
+  }
+
+  @NotNull
+  public CommitSelectController getCommitSelectController() {
+    return commitSelectController;
+  }
+
+  @NotNull
+  public GraphPrintCell getGraphPrintCell(final int rowIndex) {
+    final PrePrintCellModel prePrintCellModel = new PrePrintCellModel(hideLongEdges, layoutModel, rowIndex, selectController,
+                                                                      commitSelectController);
+
+    return new GraphPrintCell() {
+      @Override
+      public int countCell() {
+        return prePrintCellModel.getCountCells();
+      }
+
+      @NotNull
+      @Override
+      public List<ShortEdge> getUpEdges() {
+        return GraphPrintCellModelImpl.this.getUpEdges(rowIndex);
+      }
+
+      @NotNull
+      @Override
+      public List<ShortEdge> getDownEdges() {
+        return prePrintCellModel.downShortEdges();
+      }
+
+      @NotNull
+      @Override
+      public List<SpecialPrintElement> getSpecialPrintElements() {
+        return prePrintCellModel.getSpecialPrintElements();
+      }
+    };
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/impl/PrePrintCellModel.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/impl/PrePrintCellModel.java
new file mode 100644 (file)
index 0000000..253b075
--- /dev/null
@@ -0,0 +1,167 @@
+package org.hanuna.gitalk.printmodel.impl;
+
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.printmodel.CommitSelectController;
+import org.hanuna.gitalk.printmodel.SelectController;
+import org.hanuna.gitalk.printmodel.ShortEdge;
+import org.hanuna.gitalk.printmodel.SpecialPrintElement;
+import org.hanuna.gitalk.printmodel.layout.LayoutModel;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * @author erokhins
+ */
+class PrePrintCellModel {
+  private final GraphElementsVisibilityController visibilityController;
+  private List<GraphElement> visibleElementsInThisRow;
+  private final int rowIndex;
+  private final SelectController selectController;
+  private final CommitSelectController commitSelectController;
+
+  public PrePrintCellModel(boolean hideLongEdge,
+                           @NotNull LayoutModel layoutModel,
+                           int rowIndex,
+                           @NotNull SelectController selectController, CommitSelectController commitSelectController) {
+    this.commitSelectController = commitSelectController;
+    visibilityController = new GraphElementsVisibilityController(hideLongEdge, layoutModel);
+    this.rowIndex = rowIndex;
+    this.selectController = selectController;
+    visibleElementsInThisRow = visibilityController.visibleElements(rowIndex);
+  }
+
+  public PrePrintCellModel(@NotNull LayoutModel layoutModel,
+                           int rowIndex,
+                           @NotNull SelectController selectController,
+                           CommitSelectController commitSelectController) {
+    this(true, layoutModel, rowIndex, selectController, commitSelectController);
+  }
+
+  public int getCountCells() {
+    return visibleElementsInThisRow.size();
+  }
+
+  private boolean isMarked(Edge edge) {
+    return commitSelectController.isSelected(edge.getUpNode()) && commitSelectController.isSelected(edge.getDownNode());
+  }
+
+  @NotNull
+  public List<SpecialPrintElement> getSpecialPrintElements() {
+    List<SpecialPrintElement> specialPrintElements = new ArrayList<SpecialPrintElement>();
+
+    for (int i = 0; i < visibleElementsInThisRow.size(); i++) {
+      GraphElement element = visibleElementsInThisRow.get(i);
+      Node node = element.getNode();
+      if (node != null) {
+        if (node.getType() == Node.NodeType.COMMIT_NODE) {
+          int dragAndDropSelect = 0;
+          if (node == commitSelectController.getDragAndDropNode()) {
+            if (commitSelectController.isAbove()) {
+              dragAndDropSelect = 1;
+            } else {
+              dragAndDropSelect = -1;
+            }
+          }
+          specialPrintElements
+            .add(new SpecialPrintElement(node, i, SpecialPrintElement.Type.COMMIT_NODE, selectController.isSelected(node),
+                                         commitSelectController.isSelected(node), dragAndDropSelect));
+        }
+      }
+      else {
+        Edge edge = element.getEdge();
+        if (edge == null) {
+          throw new IllegalStateException();
+        }
+        switch (visibilityController.visibilityTypeEdge(edge, rowIndex)) {
+          case HIDE:
+            // do nothing
+            break;
+          case USUAL:
+            // do nothing
+            break;
+          case LAST_VISIBLE:
+            specialPrintElements
+              .add(new SpecialPrintElement(edge, i, SpecialPrintElement.Type.DOWN_ARROW, selectController.isSelected(edge), isMarked(edge),
+                                           0));
+            break;
+          case FIRST_VISIBLE:
+            specialPrintElements
+              .add(new SpecialPrintElement(edge, i, SpecialPrintElement.Type.UP_ARROW, selectController.isSelected(edge), isMarked(edge),
+                                           0));
+            break;
+          default:
+            throw new IllegalStateException();
+        }
+      }
+    }
+    return Collections.unmodifiableList(specialPrintElements);
+  }
+
+  @NotNull
+  public List<ShortEdge> downShortEdges() {
+    GetterGraphElementPosition getter = new GetterGraphElementPosition(visibilityController.visibleElements(rowIndex + 1));
+
+    List<ShortEdge> shortEdges = new ArrayList<ShortEdge>();
+    // start with add shortEdges from Node
+    for (int p = 0; p < visibleElementsInThisRow.size(); p++) {
+      Node node = visibleElementsInThisRow.get(p).getNode();
+      if (node != null) {
+        for (Edge edge : node.getDownEdges()) {
+          int to = getter.getPosition(edge);
+          assert to != -1;
+          shortEdges.add(new ShortEdge(edge, p, to, selectController.isSelected(edge), isMarked(edge)));
+        }
+      }
+    }
+    for (int p = 0; p < visibleElementsInThisRow.size(); p++) {
+      Edge edge = visibleElementsInThisRow.get(p).getEdge();
+      if (edge != null) {
+        int to = getter.getPosition(edge);
+        if (to >= 0) {
+          shortEdges.add(new ShortEdge(edge, p, to, selectController.isSelected(edge), isMarked(edge)));
+        }
+      }
+    }
+
+    return Collections.unmodifiableList(shortEdges);
+  }
+
+  private static class GetterGraphElementPosition {
+    private final Map<Node, Integer> mapNodes = new HashMap<Node, Integer>();
+
+    public GetterGraphElementPosition(List<GraphElement> graphElements) {
+      mapNodes.clear();
+      for (int p = 0; p < graphElements.size(); p++) {
+        mapNodes.put(getDownNode(graphElements.get(p)), p);
+      }
+    }
+
+    private Node getDownNode(@NotNull GraphElement element) {
+      Node node = element.getNode();
+      if (node != null) {
+        return node;
+      }
+      else {
+        Edge edge = element.getEdge();
+        if (edge == null) {
+          throw new IllegalStateException();
+        }
+        return edge.getDownNode();
+      }
+    }
+
+    public int getPosition(Edge edge) {
+      Integer p = mapNodes.get(edge.getDownNode());
+      if (p == null) {
+        // i.e. hide branch
+        return -1;
+      }
+      return p;
+    }
+
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/LayoutModel.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/LayoutModel.java
new file mode 100644 (file)
index 0000000..389fc79
--- /dev/null
@@ -0,0 +1,42 @@
+package org.hanuna.gitalk.printmodel.layout;
+
+import org.hanuna.gitalk.common.compressedlist.CompressedList;
+import org.hanuna.gitalk.common.compressedlist.RuntimeGenerateCompressedList;
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.hanuna.gitalk.common.compressedlist.generator.Generator;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class LayoutModel {
+  private final Graph graph;
+  private CompressedList<LayoutRow> layoutRowCompressedList;
+  private final Generator<LayoutRow> generator;
+
+
+  public LayoutModel(@NotNull Graph graph) {
+    this.graph = graph;
+    this.generator = new LayoutRowGenerator(graph);
+    build();
+  }
+
+  private void build() {
+    List<NodeRow> rows = graph.getNodeRows();
+    layoutRowCompressedList = new RuntimeGenerateCompressedList<LayoutRow>(generator, rows.size(), 100);
+  }
+
+
+  @NotNull
+  public List<LayoutRow> getLayoutRows() {
+    return layoutRowCompressedList.getList();
+  }
+
+  public void recalculate(@NotNull UpdateRequest updateRequest) {
+    layoutRowCompressedList.recalculate(updateRequest);
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/LayoutRow.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/LayoutRow.java
new file mode 100644 (file)
index 0000000..3ddb63f
--- /dev/null
@@ -0,0 +1,17 @@
+package org.hanuna.gitalk.printmodel.layout;
+
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public interface LayoutRow {
+  @NotNull
+  public List<GraphElement> getOrderedGraphElements();
+
+  public NodeRow getGraphNodeRow();
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/LayoutRowGenerator.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/LayoutRowGenerator.java
new file mode 100644 (file)
index 0000000..43f3a8f
--- /dev/null
@@ -0,0 +1,125 @@
+package org.hanuna.gitalk.printmodel.layout;
+
+import org.hanuna.gitalk.common.compressedlist.generator.AbstractGenerator;
+import org.hanuna.gitalk.graph.Graph;
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * @author erokhins
+ */
+class LayoutRowGenerator extends AbstractGenerator<LayoutRow, MutableLayoutRow> {
+  private final Graph graph;
+
+  public LayoutRowGenerator(@NotNull Graph graph) {
+    this.graph = graph;
+  }
+
+  @NotNull
+  @Override
+  protected MutableLayoutRow createMutable(@NotNull LayoutRow cellRow) {
+    return new MutableLayoutRow(cellRow);
+  }
+
+  @NotNull
+  private List<Edge> orderAddEdges(@NotNull List<Edge> edges) {
+    if (edges.size() <= 1) {
+      return edges;
+    }
+    else {
+      List<Edge> sortEdges = new ArrayList<Edge>(edges);
+      Collections.sort(sortEdges, new Comparator<Edge>() {
+        @Override
+        public int compare(Edge o1, Edge o2) {
+          if (o1.getDownNode().getRowIndex() > o2.getDownNode().getRowIndex()) {
+            return -1;
+          }
+          else {
+            return 1;
+          }
+        }
+      });
+      return sortEdges;
+    }
+  }
+
+  @NotNull
+  @Override
+  protected MutableLayoutRow oneStep(@NotNull MutableLayoutRow row) {
+    int newRowIndex = row.getGraphNodeRow().getRowIndex() + 1;
+    if (newRowIndex == graph.getNodeRows().size()) {
+      throw new NoSuchElementException();
+    }
+    List<GraphElement> layoutRow = row.getModifiableOrderedGraphElements();
+    Set<Node> addedNodeInNextRow = new HashSet<Node>();
+    for (ListIterator<GraphElement> iterator = layoutRow.listIterator(); iterator.hasNext(); ) {
+      GraphElement element = iterator.next();
+      Node node = element.getNode();
+      if (node != null) {
+        List<Edge> edges = node.getDownEdges();
+        if (edges.size() == 0) {
+          iterator.remove();
+        }
+        else {
+          iterator.remove();
+          for (Edge edge : orderAddEdges(edges)) {
+            Node downNode = edge.getDownNode();
+            if (downNode.getRowIndex() == newRowIndex) {
+              if (!addedNodeInNextRow.contains(downNode)) {
+                iterator.add(downNode);
+                addedNodeInNextRow.add(downNode);
+              }
+            }
+            else {
+              iterator.add(edge);
+            }
+          }
+        }
+      }
+      else {
+        Edge edge = element.getEdge();
+        if (edge == null) {
+          throw new IllegalStateException("unexpected element class");
+        }
+        if (edge.getDownNode().getRowIndex() == newRowIndex) {
+          if (!addedNodeInNextRow.contains(edge.getDownNode())) {
+            iterator.set(edge.getDownNode());
+            addedNodeInNextRow.add(edge.getDownNode());
+          }
+          else {
+            iterator.remove();
+          }
+        }
+      }
+    }
+    NodeRow nextGraphRow = graph.getNodeRows().get(newRowIndex);
+    for (Node node : nextGraphRow.getNodes()) {
+      if (node.getUpEdges().isEmpty()) {
+        layoutRow.add(node);
+      }
+    }
+    row.setNodeRow(nextGraphRow);
+    return row;
+  }
+
+  @NotNull
+  @Override
+  public LayoutRow generateFirst() {
+    List<NodeRow> rows = graph.getNodeRows();
+    assert !rows.isEmpty();
+
+    NodeRow firstRow = rows.get(0);
+    MutableLayoutRow firstCellRow = new MutableLayoutRow();
+    firstCellRow.setNodeRow(firstRow);
+    List<GraphElement> editableLayoutRow = firstCellRow.getModifiableOrderedGraphElements();
+    for (Node node : firstRow.getNodes()) {
+      editableLayoutRow.add(node);
+    }
+    return firstCellRow;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/MutableLayoutRow.java b/platform/dvcs/vcs-log/vcs-log-print-model/src/org/hanuna/gitalk/printmodel/layout/MutableLayoutRow.java
new file mode 100644 (file)
index 0000000..cf7a467
--- /dev/null
@@ -0,0 +1,47 @@
+package org.hanuna.gitalk.printmodel.layout;
+
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.NodeRow;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+class MutableLayoutRow implements LayoutRow {
+  private final List<GraphElement> graphElements;
+  private NodeRow nodeRow;
+
+  public MutableLayoutRow() {
+    graphElements = new LinkedList<GraphElement>();
+  }
+
+  public MutableLayoutRow(@NotNull LayoutRow layoutRow) {
+    this.graphElements = new LinkedList<GraphElement>(layoutRow.getOrderedGraphElements());
+    this.nodeRow = layoutRow.getGraphNodeRow();
+  }
+
+  // modifiable List
+  @NotNull
+  public List<GraphElement> getModifiableOrderedGraphElements() {
+    return graphElements;
+  }
+
+  public void setNodeRow(@NotNull NodeRow nodeRow) {
+    this.nodeRow = nodeRow;
+  }
+
+  @NotNull
+  @Override
+  public List<GraphElement> getOrderedGraphElements() {
+    return Collections.unmodifiableList(graphElements);
+  }
+
+  @Override
+  public NodeRow getGraphNodeRow() {
+    return nodeRow;
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/test/org/hanuna/gitalk/printmodel/LayoutTestUtils.java b/platform/dvcs/vcs-log/vcs-log-print-model/test/org/hanuna/gitalk/printmodel/LayoutTestUtils.java
new file mode 100644 (file)
index 0000000..f990881
--- /dev/null
@@ -0,0 +1,59 @@
+package org.hanuna.gitalk.printmodel;
+
+import org.hanuna.gitalk.graph.elements.Edge;
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.printmodel.layout.LayoutModel;
+import org.hanuna.gitalk.printmodel.layout.LayoutRow;
+
+import java.util.List;
+
+/**
+ * @author erokhins
+ */
+public class LayoutTestUtils {
+  public static String toShortStr(Node node) {
+    return node.getCommitHash().toStrHash();
+  }
+
+  public static String toShortStr(GraphElement element) {
+    Node node = element.getNode();
+    if (node != null) {
+      return toShortStr(node);
+    }
+    else {
+      Edge edge = element.getEdge();
+      if (edge == null) {
+        throw new IllegalStateException();
+      }
+      return toShortStr(edge.getUpNode()) + ":" + toShortStr(edge.getDownNode());
+    }
+  }
+
+  public static String toStr(LayoutRow row) {
+    List<GraphElement> orderedGraphElements = row.getOrderedGraphElements();
+    if (orderedGraphElements.isEmpty()) {
+      return "";
+    }
+    StringBuilder s = new StringBuilder();
+    s.append(toShortStr(orderedGraphElements.get(0)));
+    for (int i = 1; i < orderedGraphElements.size(); i++) {
+      s.append(" ").append(toShortStr(orderedGraphElements.get(i)));
+    }
+    return s.toString();
+  }
+
+  public static String toStr(LayoutModel layoutModel) {
+    List<LayoutRow> cells = layoutModel.getLayoutRows();
+    if (cells.isEmpty()) {
+      return "";
+    }
+    StringBuilder s = new StringBuilder();
+    s.append(toStr(cells.get(0)));
+    for (int i = 1; i < cells.size(); i++) {
+      s.append("\n").append(toStr(cells.get(i)));
+    }
+
+    return s.toString();
+  }
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/test/org/hanuna/gitalk/printmodel/cells/builder/LayoutModelBuilderTest.java b/platform/dvcs/vcs-log/vcs-log-print-model/test/org/hanuna/gitalk/printmodel/cells/builder/LayoutModelBuilderTest.java
new file mode 100644 (file)
index 0000000..16a0628
--- /dev/null
@@ -0,0 +1,54 @@
+package org.hanuna.gitalk.printmodel.cells.builder;
+
+import com.intellij.vcs.log.CommitParents;
+import org.hanuna.gitalk.graph.mutable.GraphBuilder;
+import org.hanuna.gitalk.log.parser.SimpleCommitListParser;
+import org.hanuna.gitalk.printmodel.layout.LayoutModel;
+import com.intellij.vcs.log.Ref;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static org.hanuna.gitalk.printmodel.LayoutTestUtils.toStr;
+
+/**
+ * @author erokhins
+ */
+public class LayoutModelBuilderTest {
+  private void runTest(String input, String out) throws IOException {
+    SimpleCommitListParser parser = new SimpleCommitListParser(new StringReader(input));
+    List<CommitParents> commitParentses = parser.readAllCommits();
+    LayoutModel layoutModel = new LayoutModel(GraphBuilder.build(commitParentses, Collections.<Ref>emptyList()));
+    assertEquals(out, toStr(layoutModel));
+  }
+
+  @Test
+  public void test1() throws IOException {
+    runTest("a0|-a3 a1\n" +
+            "a1|-a2 a4\n" +
+            "a2|-a3 a5 a8\n" +
+            "a3|-a6\n" +
+            "a4|-a7\n" +
+            "a5|-a7\n" +
+            "a6|-a7\n" +
+            "a7|-\n" +
+            "a8|-",
+
+            "a0\n" +
+            "a0:a3 a1\n" +
+            "a0:a3 a1:a4 a2\n" +
+            "a3 a1:a4 a2:a8 a2:a5\n" +
+            "a3:a6 a4 a2:a8 a2:a5\n" +
+            "a3:a6 a4:a7 a2:a8 a5\n" +
+            "a6 a7 a2:a8\n" +
+            "a7 a2:a8\n" +
+            "a8");
+
+  }
+
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-print-model/vcs-log-print-model.iml b/platform/dvcs/vcs-log/vcs-log-print-model/vcs-log-print-model.iml
new file mode 100644 (file)
index 0000000..c5bedbf
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="vcs-log-graph" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/lib/annotations.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="vcs-log-common" />
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/lib/junit-4.10.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="vcs-log-api" scope="TEST" />
+  </component>
+</module>
+
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/apply-16.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/apply-16.png
new file mode 100644 (file)
index 0000000..e8927b4
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/apply-16.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/arrow-32.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/arrow-32.png
new file mode 100644 (file)
index 0000000..3867b9a
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/arrow-32.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/bricks-multicolor-32.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/bricks-multicolor-32.png
new file mode 100644 (file)
index 0000000..9f40e5f
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/bricks-multicolor-32.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/bricks-user-32.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/bricks-user-32.png
new file mode 100644 (file)
index 0000000..e30f604
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/bricks-user-32.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/cancel-16.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/cancel-16.png
new file mode 100644 (file)
index 0000000..d6d2863
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/cancel-16.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/cherry-pick-32.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/cherry-pick-32.png
new file mode 100644 (file)
index 0000000..d705810
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/cherry-pick-32.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/edit-32.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/edit-32.png
new file mode 100644 (file)
index 0000000..4331ce4
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/edit-32.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/fixup-32.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/fixup-32.png
new file mode 100644 (file)
index 0000000..150c8f5
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/fixup-32.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/forbidden-32.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/forbidden-32.png
new file mode 100644 (file)
index 0000000..e8d2f5a
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/forbidden-32.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/move-32.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/move-32.png
new file mode 100644 (file)
index 0000000..cd7c217
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/move-32.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/spider-1-16.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/spider-1-16.png
new file mode 100644 (file)
index 0000000..f79a856
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/spider-1-16.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/spider-16-2.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/spider-16-2.png
new file mode 100644 (file)
index 0000000..f69f4c4
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/spider-16-2.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/spider-16.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/spider-16.png
new file mode 100644 (file)
index 0000000..ce80f0f
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/spider-16.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/icons/web-16.png b/platform/dvcs/vcs-log/vcs-log-ui/icons/web-16.png
new file mode 100644 (file)
index 0000000..bea4be0
Binary files /dev/null and b/platform/dvcs/vcs-log/vcs-log-ui/icons/web-16.png differ
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/DragDropListener.java b/platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/DragDropListener.java
new file mode 100644 (file)
index 0000000..693b7e8
--- /dev/null
@@ -0,0 +1,53 @@
+package org.hanuna.gitalk.ui;
+
+import org.hanuna.gitalk.graph.elements.Node;
+
+import java.awt.event.MouseEvent;
+import java.util.List;
+
+public class DragDropListener {
+
+  public static final DragDropListener EMPTY = new DragDropListener();
+
+  public static class Handler {
+
+    private static final Handler EMPTY = new Handler();
+
+    public void above(int rowIndex, Node commit, MouseEvent e, List<Node> commitsBeingDragged) {
+    }
+
+    public void below(int rowIndex, Node commit, MouseEvent e, List<Node> commitsBeingDragged) {
+
+    }
+
+    public void over(int rowIndex, Node commit, MouseEvent e, List<Node> commitsBeingDragged) {
+
+    }
+
+    public void overNode(int rowIndex, Node commit, MouseEvent e, List<Node> commitsBeingDragged) {
+
+    }
+  }
+
+  public Handler drag() {
+    return Handler.EMPTY;
+  }
+
+  public Handler drop() {
+    return Handler.EMPTY;
+  }
+
+  public void draggingStarted(List<Node> commitsBeingDragged) {
+
+  }
+
+  public void draggingCanceled(List<Node> commitsBeingDragged) {
+
+  }
+
+  // This method totally should not be here
+  public void reword(int row, String message) {
+
+  }
+
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/GitLogIcons.java b/platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/GitLogIcons.java
new file mode 100644 (file)
index 0000000..82110df
--- /dev/null
@@ -0,0 +1,21 @@
+package org.hanuna.gitalk.ui;
+
+import com.intellij.openapi.util.IconLoader;
+
+import javax.swing.Icon;
+
+public class GitLogIcons {
+  public static final Icon CHERRY_PICK = IconLoader.getIcon("/cherry-pick-32.png");
+  public static final Icon MOVE = IconLoader.getIcon("/move-32.png");
+  public static final Icon REBASE = IconLoader.getIcon("/arrow-32.png");
+  public static final Icon REBASE_INTERACTIVE = IconLoader.getIcon("/edit-32.png");
+  public static final Icon FIX_UP = IconLoader.getIcon("/fixup-32.png");
+  public static final Icon FORBIDDEN = IconLoader.getIcon("/forbidden-32.png");
+
+  public static final Icon APPLY = IconLoader.getIcon("/apply-16.png");
+  public static final Icon CANCEL = IconLoader.getIcon("/cancel-16.png");
+  //public static final Icon SPIDER = IconLoader.getIcon("/spider-16.png");
+  public static final Icon SPIDER = IconLoader.getIcon("/spider-1-16.png");
+  //public static final Icon SPIDER = IconLoader.getIcon("/spider-16-2.png");
+  public static final Icon WEB = IconLoader.getIcon("/web-16.png");
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/RefAction.java b/platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/RefAction.java
new file mode 100644 (file)
index 0000000..dedc9fa
--- /dev/null
@@ -0,0 +1,8 @@
+package org.hanuna.gitalk.ui;
+
+
+import com.intellij.vcs.log.Ref;
+
+public interface RefAction {
+  void perform(Ref ref);
+}
diff --git a/platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/VcsLogUI.java b/platform/dvcs/vcs-log/vcs-log-ui/src/org/hanuna/gitalk/ui/VcsLogUI.java
new file mode 100644 (file)
index 0000000..e899051
--- /dev/null
@@ -0,0 +1,185 @@
+package org.hanuna.gitalk.ui;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.vcs.log.Hash;
+import org.hanuna.gitalk.common.compressedlist.UpdateRequest;
+import org.hanuna.gitalk.common.compressedlist.VcsLogLogger;
+import org.hanuna.gitalk.data.DataPack;
+import org.hanuna.gitalk.data.VcsLogDataHolder;
+import org.hanuna.gitalk.graph.elements.GraphElement;
+import org.hanuna.gitalk.graph.elements.Node;
+import org.hanuna.gitalk.graphmodel.FragmentManager;
+import org.hanuna.gitalk.graphmodel.GraphFragment;
+import org.hanuna.gitalk.printmodel.SelectController;
+import org.hanuna.gitalk.ui.frame.MainFrame;
+import org.hanuna.gitalk.ui.tables.GraphTableModel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.table.TableModel;
+
+/**
+ * @author erokhins
+ */
+public class VcsLogUI {
+
+  private static final Logger LOG = VcsLogLogger.LOG;
+
+  @NotNull private final VcsLogDataHolder myLogDataHolder;
+  @NotNull private final MainFrame myMainFrame;
+