mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
New merge dialog initial implementation
This commit is contained in:
@@ -530,6 +530,9 @@ vcs.enable.partial.changelists.redo.restartRequired=true
|
|||||||
vcs.skip.single.default.changelist=true
|
vcs.skip.single.default.changelist=true
|
||||||
vcs.skip.single.default.changelist.description=Don't show changelist node in Local Changes view when only the Default changelist exists
|
vcs.skip.single.default.changelist.description=Don't show changelist node in Local Changes view when only the Default changelist exists
|
||||||
|
|
||||||
|
vcs.new.multiple.file.merge=false
|
||||||
|
vcs.new.multiple.file.merge.description=Use new implementation of multiple files merge dialog
|
||||||
|
|
||||||
psi.incremental.reparse.depth.limit=1000
|
psi.incremental.reparse.depth.limit=1000
|
||||||
psi.deferIconLoading=true
|
psi.deferIconLoading=true
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ public final class VcsConfiguration implements PersistentStateComponent<VcsConfi
|
|||||||
public boolean UPDATE_GROUP_BY_CHANGELIST = false;
|
public boolean UPDATE_GROUP_BY_CHANGELIST = false;
|
||||||
public boolean UPDATE_FILTER_BY_SCOPE = false;
|
public boolean UPDATE_FILTER_BY_SCOPE = false;
|
||||||
public boolean SHOW_FILE_HISTORY_AS_TREE = false;
|
public boolean SHOW_FILE_HISTORY_AS_TREE = false;
|
||||||
|
public boolean GROUP_MULTIFILE_MERGE_BY_DIRECTORY = false;
|
||||||
|
|
||||||
private static final int MAX_STORED_MESSAGES = 25;
|
private static final int MAX_STORED_MESSAGES = 25;
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.intellij.openapi.ui.Messages;
|
|||||||
import com.intellij.openapi.util.Disposer;
|
import com.intellij.openapi.util.Disposer;
|
||||||
import com.intellij.openapi.util.Getter;
|
import com.intellij.openapi.util.Getter;
|
||||||
import com.intellij.openapi.util.Pair;
|
import com.intellij.openapi.util.Pair;
|
||||||
|
import com.intellij.openapi.util.registry.Registry;
|
||||||
import com.intellij.openapi.util.text.StringUtil;
|
import com.intellij.openapi.util.text.StringUtil;
|
||||||
import com.intellij.openapi.vcs.*;
|
import com.intellij.openapi.vcs.*;
|
||||||
import com.intellij.openapi.vcs.actions.AnnotateToggleAction;
|
import com.intellij.openapi.vcs.actions.AnnotateToggleAction;
|
||||||
@@ -45,6 +46,7 @@ import com.intellij.openapi.vcs.history.VcsRevisionNumber;
|
|||||||
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
||||||
import com.intellij.openapi.vcs.merge.MergeProvider;
|
import com.intellij.openapi.vcs.merge.MergeProvider;
|
||||||
import com.intellij.openapi.vcs.merge.MultipleFileMergeDialog;
|
import com.intellij.openapi.vcs.merge.MultipleFileMergeDialog;
|
||||||
|
import com.intellij.openapi.vcs.merge.MultipleFileMergeDialog2;
|
||||||
import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
|
import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
|
||||||
import com.intellij.openapi.vcs.versionBrowser.ChangesBrowserSettingsEditor;
|
import com.intellij.openapi.vcs.versionBrowser.ChangesBrowserSettingsEditor;
|
||||||
import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
|
import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
|
||||||
@@ -494,10 +496,18 @@ public class AbstractVcsHelperImpl extends AbstractVcsHelper {
|
|||||||
@NotNull MergeDialogCustomizer mergeDialogCustomizer) {
|
@NotNull MergeDialogCustomizer mergeDialogCustomizer) {
|
||||||
if (files.isEmpty()) return Collections.emptyList();
|
if (files.isEmpty()) return Collections.emptyList();
|
||||||
VfsUtil.markDirtyAndRefresh(false, false, false, ArrayUtil.toObjectArray(files, VirtualFile.class));
|
VfsUtil.markDirtyAndRefresh(false, false, false, ArrayUtil.toObjectArray(files, VirtualFile.class));
|
||||||
final MultipleFileMergeDialog fileMergeDialog = new MultipleFileMergeDialog(myProject, files, provider, mergeDialogCustomizer);
|
if (Registry.is("vcs.new.multiple.file.merge")) {
|
||||||
AppIcon.getInstance().requestAttention(myProject, true);
|
final MultipleFileMergeDialog2 fileMergeDialog = new MultipleFileMergeDialog2(myProject, files, provider, mergeDialogCustomizer);
|
||||||
fileMergeDialog.show();
|
AppIcon.getInstance().requestAttention(myProject, true);
|
||||||
return fileMergeDialog.getProcessedFiles();
|
fileMergeDialog.show();
|
||||||
|
return fileMergeDialog.getProcessedFiles();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final MultipleFileMergeDialog fileMergeDialog = new MultipleFileMergeDialog(myProject, files, provider, mergeDialogCustomizer);
|
||||||
|
AppIcon.getInstance().requestAttention(myProject, true);
|
||||||
|
fileMergeDialog.show();
|
||||||
|
return fileMergeDialog.getProcessedFiles();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openCommittedChangesTab(final AbstractVcs vcs,
|
public void openCommittedChangesTab(final AbstractVcs vcs,
|
||||||
|
|||||||
@@ -0,0 +1,468 @@
|
|||||||
|
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||||
|
package com.intellij.openapi.vcs.merge
|
||||||
|
|
||||||
|
import com.intellij.CommonBundle
|
||||||
|
import com.intellij.diff.DiffManager
|
||||||
|
import com.intellij.diff.DiffRequestFactory
|
||||||
|
import com.intellij.diff.InvalidDiffRequestException
|
||||||
|
import com.intellij.diff.merge.MergeRequest
|
||||||
|
import com.intellij.diff.merge.MergeResult
|
||||||
|
import com.intellij.diff.merge.MergeUtil
|
||||||
|
import com.intellij.diff.util.DiffUtil
|
||||||
|
import com.intellij.icons.AllIcons
|
||||||
|
import com.intellij.ide.presentation.VirtualFilePresentation
|
||||||
|
import com.intellij.openapi.actionSystem.*
|
||||||
|
import com.intellij.openapi.command.WriteCommandAction.writeCommandAction
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
|
import com.intellij.openapi.diff.impl.mergeTool.MergeVersion
|
||||||
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.project.ex.ProjectManagerEx
|
||||||
|
import com.intellij.openapi.ui.DialogWrapper
|
||||||
|
import com.intellij.openapi.ui.Messages
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
|
import com.intellij.openapi.vcs.VcsBundle
|
||||||
|
import com.intellij.openapi.vcs.VcsConfiguration
|
||||||
|
import com.intellij.openapi.vcs.VcsException
|
||||||
|
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager
|
||||||
|
import com.intellij.openapi.vfs.VfsUtil
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.ui.ColoredTreeCellRenderer
|
||||||
|
import com.intellij.ui.DoubleClickListener
|
||||||
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
|
import com.intellij.ui.TableSpeedSearch
|
||||||
|
import com.intellij.ui.layout.*
|
||||||
|
import com.intellij.ui.speedSearch.SpeedSearchUtil
|
||||||
|
import com.intellij.ui.treeStructure.treetable.ListTreeTableModelOnColumns
|
||||||
|
import com.intellij.ui.treeStructure.treetable.TreeTable
|
||||||
|
import com.intellij.ui.treeStructure.treetable.TreeTableModel
|
||||||
|
import com.intellij.util.containers.Convertor
|
||||||
|
import com.intellij.util.ui.ColumnInfo
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
|
import com.intellij.util.ui.tree.TreeUtil
|
||||||
|
import org.jetbrains.annotations.NonNls
|
||||||
|
import java.awt.event.ActionEvent
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import javax.swing.*
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yole
|
||||||
|
*/
|
||||||
|
open class MultipleFileMergeDialog2(
|
||||||
|
private val project: Project?,
|
||||||
|
files: List<VirtualFile>,
|
||||||
|
private val mergeProvider: MergeProvider,
|
||||||
|
private val mergeDialogCustomizer: MergeDialogCustomizer
|
||||||
|
) : DialogWrapper(project) {
|
||||||
|
|
||||||
|
private var files = files.toMutableList()
|
||||||
|
private val mergeSession = (mergeProvider as? MergeProvider2)?.createMergeSession(files)
|
||||||
|
val processedFiles = mutableListOf<VirtualFile>()
|
||||||
|
private lateinit var table: TreeTable
|
||||||
|
private lateinit var acceptYoursButton: JButton
|
||||||
|
private lateinit var acceptTheirsButton: JButton
|
||||||
|
private lateinit var mergeButton: JButton
|
||||||
|
private val tableModel = ListTreeTableModelOnColumns(DefaultMutableTreeNode(), createColumns())
|
||||||
|
private val binaryFiles = files.filter { it.fileType.isBinary || mergeProvider.isBinary(it) }
|
||||||
|
private val projectManager = ProjectManagerEx.getInstanceEx()
|
||||||
|
private var groupByDirectory = project?.let { VcsConfiguration.getInstance(it).GROUP_MULTIFILE_MERGE_BY_DIRECTORY } ?: false
|
||||||
|
|
||||||
|
private val virtualFileRenderer = object : ColoredTreeCellRenderer() {
|
||||||
|
override fun customizeCellRenderer(tree: JTree,
|
||||||
|
value: Any?,
|
||||||
|
selected: Boolean,
|
||||||
|
expanded: Boolean,
|
||||||
|
leaf: Boolean,
|
||||||
|
row: Int,
|
||||||
|
hasFocus: Boolean) {
|
||||||
|
val data = (value as DefaultMutableTreeNode).userObject
|
||||||
|
when (data) {
|
||||||
|
is String -> {
|
||||||
|
icon = AllIcons.Nodes.Folder
|
||||||
|
val parent = value.parent as DefaultMutableTreeNode
|
||||||
|
val parentPath = parent.userObject as String?
|
||||||
|
append(if (parentPath == null) FileUtil.getLocationRelativeToUserHome(data) else data.substring(parentPath.length + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
is VirtualFile -> {
|
||||||
|
icon = VirtualFilePresentation.getIcon(data)
|
||||||
|
append(data.name, SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||||
|
if (data in binaryFiles) {
|
||||||
|
append(" [${VcsBundle.message("multiple.file.merge.type.binary")}]", SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||||
|
}
|
||||||
|
if (!groupByDirectory) {
|
||||||
|
val parent = data.parent
|
||||||
|
if (parent != null) {
|
||||||
|
append(" (" + FileUtil.getLocationRelativeToUserHome(FileUtil.toSystemDependentName(parent.presentableUrl)) + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpeedSearchUtil.applySpeedSearchHighlighting(this@MultipleFileMergeDialog2.table, this, true, selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun calcFocusedState() = table.hasFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
projectManager.blockReloadingProjectOnExternalChanges()
|
||||||
|
title = mergeDialogCustomizer.multipleFileDialogTitle
|
||||||
|
virtualFileRenderer.font = UIUtil.getListFont()
|
||||||
|
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
init()
|
||||||
|
|
||||||
|
updateColumnSizes()
|
||||||
|
updateTree()
|
||||||
|
table.tree.selectionModel.addTreeSelectionListener { updateButtonState() }
|
||||||
|
table.selectionModel.setSelectionInterval(0, 0)
|
||||||
|
object : DoubleClickListener() {
|
||||||
|
override fun onDoubleClick(event: MouseEvent): Boolean {
|
||||||
|
showMergeDialog()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}.installOn(table)
|
||||||
|
|
||||||
|
TableSpeedSearch(table, Convertor { (it as? VirtualFile)?.name })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createCenterPanel(): JComponent {
|
||||||
|
return panel {
|
||||||
|
val description = mergeDialogCustomizer.getMultipleFileMergeDescription(processedFiles)
|
||||||
|
if (!description.isNullOrBlank()) {
|
||||||
|
row {
|
||||||
|
label(description!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row {
|
||||||
|
ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, createToolbarActionGroup(), true).component()
|
||||||
|
}
|
||||||
|
|
||||||
|
row {
|
||||||
|
scrollPane(TreeTable(tableModel).also {
|
||||||
|
table = it
|
||||||
|
it.tree.isRootVisible = false
|
||||||
|
it.setTreeCellRenderer(virtualFileRenderer)
|
||||||
|
if (tableModel.columnCount > 1) {
|
||||||
|
it.setShowColumns(true)
|
||||||
|
}
|
||||||
|
it.rowHeight = virtualFileRenderer.preferredSize.height
|
||||||
|
}, growX, growY, pushX, pushY)
|
||||||
|
|
||||||
|
cell(isVerticalFlow = true) {
|
||||||
|
JButton("Accept Yours").also {
|
||||||
|
it.addActionListener { acceptRevision(MergeSession.Resolution.AcceptedYours) }
|
||||||
|
acceptYoursButton = it
|
||||||
|
}()
|
||||||
|
JButton("Accept Theirs").also {
|
||||||
|
it.addActionListener { acceptRevision(MergeSession.Resolution.AcceptedTheirs) }
|
||||||
|
acceptTheirsButton = it
|
||||||
|
}()
|
||||||
|
val mergeAction = object : AbstractAction() {
|
||||||
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
|
showMergeDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mergeAction.putValue(DialogWrapper.DEFAULT_ACTION, java.lang.Boolean.TRUE)
|
||||||
|
createJButtonForAction(mergeAction).also {
|
||||||
|
it.text = "Merge..."
|
||||||
|
mergeButton = it
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createColumns(): Array<ColumnInfo<*, *>> {
|
||||||
|
val columns = ArrayList<ColumnInfo<*, *>>()
|
||||||
|
columns.add(object : ColumnInfo<DefaultMutableTreeNode, Any>(VcsBundle.message("multiple.file.merge.column.name")) {
|
||||||
|
override fun valueOf(node: DefaultMutableTreeNode) = node.userObject
|
||||||
|
override fun getColumnClass(): Class<*> = TreeTableModel::class.java
|
||||||
|
})
|
||||||
|
|
||||||
|
mergeSession?.mergeInfoColumns?.mapTo(columns) { ColumnInfoAdapter(it) }
|
||||||
|
return columns.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ColumnInfoAdapter(private val base: ColumnInfo<Any, Any>) : ColumnInfo<DefaultMutableTreeNode, Any>(base.name) {
|
||||||
|
override fun valueOf(node: DefaultMutableTreeNode) = (node.userObject as? VirtualFile)?.let { base.valueOf(it) }
|
||||||
|
override fun getMaxStringValue() = base.maxStringValue
|
||||||
|
override fun getAdditionalWidth() = base.additionalWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateColumnSizes() {
|
||||||
|
for ((index, columnInfo) in tableModel.columns.withIndex()) {
|
||||||
|
val column = table.columnModel.getColumn(index)
|
||||||
|
columnInfo.maxStringValue?.let {
|
||||||
|
column.maxWidth = table.getFontMetrics(table.font).stringWidth(it) + columnInfo.additionalWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createToolbarActionGroup() = DefaultActionGroup().apply {
|
||||||
|
add(object : ToggleAction("Group by Directory", "Group by Directory", AllIcons.Actions.GroupByPackage) {
|
||||||
|
override fun isSelected(e: AnActionEvent?) = groupByDirectory
|
||||||
|
|
||||||
|
override fun setSelected(e: AnActionEvent?, state: Boolean) {
|
||||||
|
groupByDirectory = state
|
||||||
|
project?.let { VcsConfiguration.getInstance(it).GROUP_MULTIFILE_MERGE_BY_DIRECTORY = groupByDirectory }
|
||||||
|
updateTree()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTree() {
|
||||||
|
val root = DefaultMutableTreeNode()
|
||||||
|
tableModel.setRoot(root)
|
||||||
|
val commonAncestor = if (groupByDirectory) VfsUtil.getCommonAncestor(files) else null
|
||||||
|
if (commonAncestor != null) {
|
||||||
|
buildGroupedFileTree(root, commonAncestor)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (file in files) {
|
||||||
|
tableModel.insertNodeInto(DefaultMutableTreeNode(file, false), root, root.childCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TreeUtil.expandAll(table.tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildGroupedFileTree(root: DefaultMutableTreeNode, commonAncestor: VirtualFile) {
|
||||||
|
val directoryNodes = mutableMapOf<String, DefaultMutableTreeNode>()
|
||||||
|
val commonAncestorNode = DefaultMutableTreeNode(commonAncestor.path)
|
||||||
|
directoryNodes[commonAncestor.path] = commonAncestorNode
|
||||||
|
tableModel.insertNodeInto(commonAncestorNode, root, 0)
|
||||||
|
|
||||||
|
for (file in files) {
|
||||||
|
var currentParentNode = commonAncestorNode
|
||||||
|
var index = commonAncestor.path.length
|
||||||
|
val lastIndex = file.path.lastIndexOf('/')
|
||||||
|
while (index < lastIndex) {
|
||||||
|
val nextIndex = file.path.indexOf('/', index)
|
||||||
|
val path = file.path.substring(0, nextIndex)
|
||||||
|
val directoryNode = directoryNodes.getOrPut(path) {
|
||||||
|
DefaultMutableTreeNode(path).also {
|
||||||
|
TreeUtil.insertNode(it, currentParentNode, tableModel, compareBy { it.userObject as String })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index = nextIndex + 1
|
||||||
|
currentParentNode = directoryNode
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeUtil.insertNode(
|
||||||
|
DefaultMutableTreeNode(file, false), currentParentNode, tableModel,
|
||||||
|
compareBy { (it.userObject as VirtualFile).path }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
collapseMiddlePaths(commonAncestorNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collapseMiddlePaths(node: DefaultMutableTreeNode) {
|
||||||
|
if (node.childCount == 1) {
|
||||||
|
val child = node.firstChild as DefaultMutableTreeNode
|
||||||
|
if (child.userObject is String) {
|
||||||
|
val parent = node.parent as DefaultMutableTreeNode
|
||||||
|
tableModel.removeNodeFromParent(node)
|
||||||
|
TreeUtil.insertNode(child, parent, tableModel, compareBy { it.userObject as String })
|
||||||
|
collapseMiddlePaths(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (child in node.children()) {
|
||||||
|
collapseMiddlePaths(child as DefaultMutableTreeNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun updateButtonState() {
|
||||||
|
val selectedFiles = getSelectedFiles()
|
||||||
|
val haveSelection = selectedFiles.any()
|
||||||
|
val haveUnmergeableFiles = selectedFiles.any { mergeSession?.canMerge(it) == false }
|
||||||
|
|
||||||
|
acceptYoursButton.isEnabled = haveSelection
|
||||||
|
acceptTheirsButton.isEnabled = haveSelection
|
||||||
|
mergeButton.isEnabled = haveSelection && !haveUnmergeableFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getSelectedFiles(): List<VirtualFile> {
|
||||||
|
return TreeUtil.collectSelectedObjectsOfType(table.tree, VirtualFile::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun beforeResolve(files: Collection<VirtualFile>): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createActions() = arrayOf(cancelAction)
|
||||||
|
|
||||||
|
|
||||||
|
override fun getCancelAction(): Action = super.getCancelAction().apply {
|
||||||
|
putValue(Action.NAME, CommonBundle.getCloseButtonText())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
projectManager.unblockReloadingProjectOnExternalChanges()
|
||||||
|
super.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNls
|
||||||
|
override fun getDimensionServiceKey() = "MultipleFileMergeDialog"
|
||||||
|
|
||||||
|
private fun acceptRevision(resolution: MergeSession.Resolution) {
|
||||||
|
FileDocumentManager.getInstance().saveAllDocuments()
|
||||||
|
val files = getSelectedFiles()
|
||||||
|
if (!beforeResolve(files)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (file in files) {
|
||||||
|
acceptFileRevision(file, resolution)
|
||||||
|
checkMarkModifiedProject(file)
|
||||||
|
markFileProcessed(file, resolution)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
LOG.warn(e)
|
||||||
|
Messages.showErrorDialog(contentPanel, "Error saving merged data: " + e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModelFromFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun acceptFileRevision(file: VirtualFile, resolution: MergeSession.Resolution) {
|
||||||
|
if (mergeSession?.canMerge(file) == false) return
|
||||||
|
|
||||||
|
if (mergeSession?.acceptFileRevision(file, resolution) == true) return
|
||||||
|
|
||||||
|
if (!DiffUtil.makeWritable(project, file)) {
|
||||||
|
throw IOException("File is read-only: " + file.presentableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isCurrent = resolution == MergeSession.Resolution.AcceptedYours
|
||||||
|
val data = mergeProvider.loadRevisions(file)
|
||||||
|
|
||||||
|
writeCommandAction(project).withName("Accept " + if (isCurrent) "Yours" else "Theirs").run<Exception> {
|
||||||
|
if (isCurrent) {
|
||||||
|
file.setBinaryContent(data.CURRENT)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
file.setBinaryContent(data.LAST)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun markFileProcessed(file: VirtualFile, resolution: MergeSession.Resolution) {
|
||||||
|
files.remove(file)
|
||||||
|
if (mergeSession != null) {
|
||||||
|
mergeSession.conflictResolvedForFile(file, resolution)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mergeProvider.conflictResolvedForFile(file)
|
||||||
|
}
|
||||||
|
processedFiles.add(file)
|
||||||
|
project?.let { VcsDirtyScopeManager.getInstance(it).fileDirty(file) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateModelFromFiles() {
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
doCancelAction()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var selIndex = table.selectionModel.minSelectionIndex
|
||||||
|
updateTree()
|
||||||
|
if (selIndex >= files.size) {
|
||||||
|
selIndex = files.size - 1
|
||||||
|
}
|
||||||
|
table.selectionModel.setSelectionInterval(selIndex, selIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showMergeDialog() {
|
||||||
|
val requestFactory = DiffRequestFactory.getInstance()
|
||||||
|
val files = getSelectedFiles()
|
||||||
|
if (files.isEmpty()) return
|
||||||
|
if (!beforeResolve(files)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (file in files) {
|
||||||
|
val mergeData: MergeData
|
||||||
|
try {
|
||||||
|
mergeData = mergeProvider.loadRevisions(file)
|
||||||
|
}
|
||||||
|
catch (ex: VcsException) {
|
||||||
|
Messages.showErrorDialog(contentPanel, "Error loading revisions to merge: " + ex.message)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergeData.CURRENT == null || mergeData.LAST == null || mergeData.ORIGINAL == null) {
|
||||||
|
Messages.showErrorDialog(contentPanel, "Error loading revisions to merge")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
val leftTitle = mergeDialogCustomizer.getLeftPanelTitle(file)
|
||||||
|
val baseTitle = mergeDialogCustomizer.getCenterPanelTitle(file)
|
||||||
|
val rightTitle = mergeDialogCustomizer.getRightPanelTitle(file, mergeData.LAST_REVISION_NUMBER)
|
||||||
|
val title = mergeDialogCustomizer.getMergeWindowTitle(file)
|
||||||
|
|
||||||
|
val byteContents = listOf(mergeData.CURRENT, mergeData.ORIGINAL, mergeData.LAST)
|
||||||
|
val contentTitles = listOf(leftTitle, baseTitle, rightTitle)
|
||||||
|
|
||||||
|
val callback = { result: MergeResult ->
|
||||||
|
val document = FileDocumentManager.getInstance().getCachedDocument(file)
|
||||||
|
if (document != null) FileDocumentManager.getInstance().saveDocument(document)
|
||||||
|
checkMarkModifiedProject(file)
|
||||||
|
|
||||||
|
if (result != MergeResult.CANCEL) {
|
||||||
|
markFileProcessed(file, getSessionResolution(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val request: MergeRequest
|
||||||
|
try {
|
||||||
|
if (mergeProvider.isBinary(file)) { // respect MIME-types in svn
|
||||||
|
request = requestFactory.createBinaryMergeRequest(project, file, byteContents, title, contentTitles, callback)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
request = requestFactory.createMergeRequest(project, file, byteContents, title, contentTitles, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
MergeUtil.putRevisionInfos(request, mergeData)
|
||||||
|
}
|
||||||
|
catch (e: InvalidDiffRequestException) {
|
||||||
|
LOG.error(e)
|
||||||
|
Messages.showErrorDialog(contentPanel, "Can't show merge dialog")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
DiffManager.getInstance().showMerge(project, request)
|
||||||
|
}
|
||||||
|
updateModelFromFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSessionResolution(result: MergeResult): MergeSession.Resolution = when (result) {
|
||||||
|
MergeResult.LEFT -> MergeSession.Resolution.AcceptedYours
|
||||||
|
MergeResult.RIGHT -> MergeSession.Resolution.AcceptedTheirs
|
||||||
|
MergeResult.RESOLVED -> MergeSession.Resolution.Merged
|
||||||
|
else -> throw IllegalArgumentException(result.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkMarkModifiedProject(file: VirtualFile) {
|
||||||
|
MergeVersion.MergeDocumentVersion.reportProjectFileChangeIfNeeded(project, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPreferredFocusedComponent(): JComponent? = table
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = Logger.getInstance(MultipleFileMergeDialog2::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user