package jetbrains.mps.vcs.diff.ui.merge; /*Generated by MPS */ import javax.swing.JPanel; import javax.swing.Icon; import com.intellij.icons.AllIcons; import com.intellij.openapi.project.Project; import org.jetbrains.mps.openapi.module.SRepository; import jetbrains.mps.vcs.diff.merge.MergeSession; import jetbrains.mps.vcs.diff.merge.MergeSessionState; import org.jetbrains.mps.openapi.model.SNodeId; import org.jetbrains.mps.openapi.model.SModel; import com.intellij.ui.JBSplitter; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.SwingConstants; import java.util.List; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.ActionToolbar; import com.intellij.openapi.diff.ex.DiffStatusBar; import com.intellij.openapi.diff.impl.util.TextDiffType; import com.intellij.openapi.actionSystem.DefaultActionGroup; import jetbrains.mps.vcs.diff.ui.common.GoToNeighbourRootActions; import java.util.Set; import jetbrains.mps.vcs.diff.changes.ModelChange; import jetbrains.mps.internal.collections.runtime.SetSequence; import java.util.HashSet; import com.intellij.diff.merge.TextMergeRequest; import java.awt.BorderLayout; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.vcs.diff.ui.common.DiffModelUtil; import jetbrains.mps.internal.collections.runtime.ListSequence; import jetbrains.mps.vcs.diff.ui.MetadataUtil; import java.util.ArrayList; import com.intellij.openapi.actionSystem.Separator; import com.intellij.ui.ScrollPaneFactory; import java.awt.Dimension; import com.intellij.openapi.util.DimensionService; import org.jetbrains.annotations.Nullable; import jetbrains.mps.internal.collections.runtime.Sequence; import jetbrains.mps.internal.collections.runtime.IWhereFilter; import jetbrains.mps.vcs.diff.changes.MetadataChange; import jetbrains.mps.smodel.ModelAccessHelper; import jetbrains.mps.util.Computable; import jetbrains.mps.vcs.diff.merge.MergeTemporaryModel; import jetbrains.mps.smodel.CopyUtil; import jetbrains.mps.extapi.model.SModelBase; import org.jetbrains.annotations.NotNull; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionPlaces; import jetbrains.mps.vcs.diff.ui.common.DiffModelTree; import jetbrains.mps.internal.collections.runtime.ISelector; import jetbrains.mps.internal.collections.runtime.ITranslator2; import jetbrains.mps.util.NameUtil; import javax.swing.event.TreeSelectionListener; import javax.swing.event.TreeSelectionEvent; import jetbrains.mps.workbench.action.BaseAction; import java.util.Arrays; import jetbrains.mps.vcs.diff.changes.ChangeType; import com.intellij.ui.SimpleTextAttributes; import jetbrains.mps.vcs.diff.changes.AddRootChange; import jetbrains.mps.vcs.diff.changes.DeleteRootChange; import jetbrains.mps.vcs.diff.ui.common.ChangeColors; public class MergeModelsPanel extends JPanel { public static final Icon APPLY_NON_CONFLICTS = AllIcons.Diff.ApplyNotConflicts; public static final Icon RESET = AllIcons.Actions.Rollback; private final Project myProject; private final SRepository myProjectRepository; private MergeSession myMergeSession; private MergeSession myMetadataMergeSession; private MergeSessionState myInitialState; private MergeSessionState myMetadataInitialState; private SNodeId myRootId; private ISaveMergedModel mySaver = new ISaveMergedModel() { public boolean save(MergeModelsPanel dialog, SModel resultModel) { return false; } }; private MergeModelsPanel.MergeModelsTree myMergeTree; private JBSplitter myPanel = new JBSplitter(true, 0.25f); private MergeRootsPane myMergeRootsPane = null; private final JComponent myNoRootPanel = new JLabel("Select root to merge", SwingConstants.CENTER); private List<AnAction> myToolbarActions; private ActionToolbar myToolbar; private DiffStatusBar myStatusBar = new DiffStatusBar(TextDiffType.DIFF_TYPES); private DefaultActionGroup myActionGroup; private GoToNeighbourRootActions myGoToNeighbourRootActions; private String[] myContentTitles = new String[0]; private Set<ModelChange> myAppliedMetadataChanges = SetSequence.fromSet(new HashSet<ModelChange>()); public MergeModelsPanel(Project project, final SModel baseModel, final SModel mineModel, final SModel repoModel, TextMergeRequest request) { super(new BorderLayout()); myProject = project; myContentTitles = request.getContentTitles().toArray(myContentTitles); assert myContentTitles.length == 3; // FIXME code below requires thorough refactoring. Models that come here are IMO loaded from disk and are not // attached to any repository, hence there's no reason to grab lock to deal with them. OTOH, there's code that // registers and exposes model artifacts with a temp module, which is part of global repository now and hence // requires model lock. myProjectRepository = ProjectHelper.getProjectRepository(project); assert myProjectRepository != null; myProjectRepository.getModelAccess().runReadAction(new Runnable() { public void run() { myMergeSession = MergeSession.createMergeSession(baseModel, mineModel, repoModel); myInitialState = myMergeSession.getCurrentState(); } }); myProjectRepository.getModelAccess().runWriteAction(new Runnable() { public void run() { DiffModelUtil.renameModelAndRegister(myMergeSession.getBaseModel(), "base"); DiffModelUtil.renameModelAndRegister(myMergeSession.getMyModel(), "mine"); DiffModelUtil.renameModelAndRegister(myMergeSession.getRepositoryModel(), "repo"); DiffModelUtil.renameModelAndRegister(myMergeSession.getResultModel(), "result"); } }); if (ListSequence.fromList(myMergeSession.getMetadataChanges()).isNotEmpty()) { myProjectRepository.getModelAccess().runWriteAction(new Runnable() { public void run() { SModel baseMetaModel = MetadataUtil.createMetadataModel(myMergeSession.getBaseModel(), "metadata_base", false); SModel mineMetaModel = MetadataUtil.createMetadataModel(myMergeSession.getMyModel(), "metadata_mine", false); SModel repoMetaModel = MetadataUtil.createMetadataModel(myMergeSession.getRepositoryModel(), "metadata_repo", false); myMetadataMergeSession = MergeSession.createMergeSession(baseMetaModel, mineMetaModel, repoMetaModel); myMetadataInitialState = myMetadataMergeSession.getCurrentState(); DiffModelUtil.renameModelAndRegister(myMetadataMergeSession.getResultModel(), "result"); } }); } myMergeSession.installResultModelListener(); myToolbarActions = ListSequence.fromListAndArray(new ArrayList<AnAction>(), new ResetState(this), new MergeNonConflictingRoots(this), Separator.getInstance(), AcceptYoursTheirs.yoursInstance(this), AcceptYoursTheirs.theirsInstance(this)); myActionGroup = new DefaultActionGroup(myToolbarActions); init(); } public List<AnAction> getToolbarActions() { return myToolbarActions; } public void setSaver(ISaveMergedModel saver) { mySaver = saver; } protected boolean saveModel(SModel resultModel) { return mySaver.save(this, resultModel); } protected void init() { myPanel.setSplitterProportionKey(getClass().getName() + "ModelTreeSplitter"); myMergeTree = new MergeModelsPanel.MergeModelsTree(); myPanel.setFirstComponent(ScrollPaneFactory.createScrollPane(myMergeTree)); myPanel.setSecondComponent(myNoRootPanel); myGoToNeighbourRootActions = new MergeModelsPanel.MyGoToNeighbourRootActions(); myGoToNeighbourRootActions.previous().registerCustomShortcutSet(GoToNeighbourRootActions.PREV_ROOT_SHORTCUT, this); myGoToNeighbourRootActions.next().registerCustomShortcutSet(GoToNeighbourRootActions.NEXT_ROOT_SHORTCUT, this); this.add(myPanel, BorderLayout.CENTER); this.add(myStatusBar, BorderLayout.SOUTH); final Dimension size = DimensionService.getInstance().getSize(getDimensionServiceKey()); if (size == null) { this.setPreferredSize(new Dimension(500, 450)); } } public String getDimensionServiceKey() { return getClass().getName(); } @Nullable public JComponent getPreferredFocusedComponent() { return myMergeTree; } public boolean saveResults() { // true - everything is OK // false - saving was cancelled applyMetadataChanges(); int result = MergeConfirmation.showMergeConfirmationAndTakeAction(this, myMergeSession, Sequence.fromIterable(myMergeSession.getAllChanges()).where(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange ch) { return !(ch instanceof MetadataChange); } }), myMetadataMergeSession, (myMetadataMergeSession == null ? null : myMetadataMergeSession.getAllChanges())); if (result == MergeConfirmation.RETURN) { return false; } if (result == MergeConfirmation.RESOLVE_AUTOMATICALLY) { myProjectRepository.getModelAccess().executeCommand(new Runnable() { public void run() { mergeNonConflictingRoots(); } }); } if (saveModel(getResultModelWithFixedId())) { unregisterModels(); return true; } return false; } public void dispose() { if (myMergeRootsPane != null) { myMergeRootsPane.dispose(); } } private SModel getResultModelWithFixedId() { SModel resultModel = new ModelAccessHelper(myProjectRepository).runReadAction(new Computable<MergeTemporaryModel>() { public MergeTemporaryModel compute() { // copy to avoid problems with de-registration jetbrains.mps.smodel.SModel resModel = CopyUtil.copyModel(as_ktyr7l_a0a0a1a0a0a0a0a0qb(myMergeSession.getResultModel(), SModelBase.class).getSModelInternal()); return new MergeTemporaryModel(resModel, false); } }); DiffModelUtil.restoreModelName(resultModel); // fix??? for (SModel m : new SModel[]{myMergeSession.getMyModel(), myMergeSession.getRepositoryModel()}) { DiffModelUtil.fixModelReferences(resultModel, m.getReference()); } return resultModel; } private void unregisterModels() { myProjectRepository.getModelAccess().runWriteAction(new Runnable() { public void run() { if (myMetadataMergeSession != null) { DiffModelUtil.unregisterModel(myMetadataMergeSession.getResultModel()); MetadataUtil.dispose(myMetadataMergeSession.getRepositoryModel()); MetadataUtil.dispose(myMetadataMergeSession.getMyModel()); MetadataUtil.dispose(myMetadataMergeSession.getBaseModel()); } DiffModelUtil.unregisterModel(myMergeSession.getResultModel()); DiffModelUtil.unregisterModel(myMergeSession.getRepositoryModel()); DiffModelUtil.unregisterModel(myMergeSession.getMyModel()); DiffModelUtil.unregisterModel(myMergeSession.getBaseModel()); } }); } /*package*/ void rebuildLater() { myMergeTree.rebuildLater(); } /*package*/ Project getProject() { return myProject; } @Nullable public SNodeId getNeighbourRoot(@NotNull SNodeId rootId, boolean next) { return myMergeTree.getNeighbourRoot(rootId, next); } public void resetCurrentRoot() { if (myMergeRootsPane == null) { return; } myMergeRootsPane.unregisterShortcuts(this); myPanel.setSecondComponent(myNoRootPanel); myMergeRootsPane.dispose(); myMergeRootsPane = null; myRootId = null; myStatusBar.setText(""); applyMetadataChanges(); } private void changeCurrentRoot(@Nullable final SNodeId rootId) { if (myMergeRootsPane != null && myRootId == rootId) { return; } applyMetadataChanges(); myRootId = rootId; final MergeSession session = (rootId == null ? myMetadataMergeSession : myMergeSession); myProjectRepository.getModelAccess().runReadAction(new Runnable() { public void run() { SNodeId nodeId = (rootId == null ? Sequence.fromIterable(myMetadataMergeSession.getAffectedRoots()).first() : rootId); if (myMergeRootsPane == null) { myMergeRootsPane = new MergeRootsPane(myProject, session, nodeId, myMergeTree.getNameForRoot(rootId), myContentTitles, myStatusBar); DefaultActionGroup actionGroup = new DefaultActionGroup(); actionGroup.addAll(myMergeRootsPane.getActions()); ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, actionGroup, true); myMergeRootsPane.registerShortcuts(MergeModelsPanel.this); JPanel panel = new JPanel(new BorderLayout()); panel.add(toolbar.getComponent(), BorderLayout.NORTH); panel.add(myMergeRootsPane.getPanel(), BorderLayout.CENTER); myPanel.setSecondComponent(panel); } else { myMergeRootsPane.setRoodId(nodeId, session); } } }); } public void setCurrentRoot(@Nullable SNodeId rootId) { myMergeTree.setSelected(rootId); changeCurrentRoot(rootId); } @Nullable public SNodeId getCurrentRoot() { return myRootId; } public Iterable<ModelChange> getApplicableChangesInNonConflictingRoots() { return Sequence.fromIterable(myMergeSession.getApplicableChangesInNonConflictingRoots()).where(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange it) { return !(it instanceof MetadataChange); } }); } public Iterable<ModelChange> getApplicableChangesInMetadata() { return myMetadataMergeSession.getApplicableChangesInNonConflictingRoots(); } public void mergeNonConflictingRoots() { myMergeSession.applyChanges(getApplicableChangesInNonConflictingRoots()); if (myMetadataMergeSession != null) { myMetadataMergeSession.applyChanges(getApplicableChangesInMetadata()); applyMetadataChanges(); } } public boolean hasNonConflictingRoots() { return Sequence.fromIterable(getApplicableChangesInNonConflictingRoots()).isNotEmpty() || myMetadataMergeSession != null && Sequence.fromIterable(getApplicableChangesInMetadata()).isNotEmpty(); } public boolean isAcceptYoursTheirsEnabled() { return Sequence.fromIterable(getModelChangesForSelection()).where(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange ch) { return !(myMergeSession.isChangeResolved(ch)); } }).isNotEmpty() || myMetadataMergeSession != null && isMetadataSelected() && Sequence.fromIterable(myMetadataMergeSession.getAllChanges()).where(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange ch) { return !(myMetadataMergeSession.isChangeResolved(ch)); } }).isNotEmpty(); } public void acceptVersionForSelectedRoots(boolean mine) { applyUnresolvedChanges(myMergeSession, getModelChangesForSelection(), mine); if (myMetadataMergeSession != null && isMetadataSelected()) { applyUnresolvedChanges(myMetadataMergeSession, myMetadataMergeSession.getAllChanges(), mine); applyMetadataChanges(); } // XXX tree.rebuildNow as model command, really? myProjectRepository.getModelAccess().executeCommand(new Runnable() { public void run() { myMergeTree.rebuildNow(); } }); } private void applyMetadataChanges() { myProjectRepository.getModelAccess().executeCommand(new Runnable() { public void run() { if (myMetadataMergeSession != null) { MetadataUtil.applyMetadataChanges(myMergeSession.getResultModel(), myMetadataMergeSession.getResultModel()); } } }); } private boolean isMetadataSelected() { return myMergeTree.getSelectedNodes(DiffModelTree.MetadataTreeNode.class, null).length == 1 || myMergeTree.getSelectedNodes(DiffModelTree.ModelTreeNode.class, null).length == 1; } private Iterable<ModelChange> getModelChangesForSelection() { if (myMergeTree.getSelectedNodes(DiffModelTree.ModelTreeNode.class, null).length == 1) { return Sequence.fromIterable(myMergeSession.getAllChanges()).where(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange ch) { return !(ch instanceof MetadataChange); } }); } else { return Sequence.fromIterable(Sequence.fromArray(myMergeTree.getSelectedNodes(DiffModelTree.RootTreeNode.class, null))).select(new ISelector<DiffModelTree.RootTreeNode, SNodeId>() { public SNodeId select(DiffModelTree.RootTreeNode rtn) { return rtn.getRootId(); } }).where(new IWhereFilter<SNodeId>() { public boolean accept(SNodeId root) { return root != null; } }).translate(new ITranslator2<SNodeId, ModelChange>() { public Iterable<ModelChange> translate(SNodeId root) { return myMergeSession.getChangesForRoot(root); } }); } } private void applyUnresolvedChanges(final MergeSession session, Iterable<ModelChange> changes, boolean mine) { final List<ModelChange> changesToApply = ListSequence.fromList(new ArrayList<ModelChange>()); final List<ModelChange> changesToExclude = ListSequence.fromList(new ArrayList<ModelChange>()); for (ModelChange change : Sequence.fromIterable(changes).where(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange ch) { return !(session.isChangeResolved(ch)); } })) { if (mine == session.isMyChange(change)) { ListSequence.fromList(changesToApply).addElement(change); } else { ListSequence.fromList(changesToExclude).addElement(change); } } myProjectRepository.getModelAccess().executeCommand(new Runnable() { public void run() { session.applyChanges(changesToApply); session.excludeChanges(changesToExclude); } }); } /*package*/ void markMetadataChangesAsApplied(Iterable<ModelChange> changes) { SetSequence.fromSet(myAppliedMetadataChanges).addSequence(Sequence.fromIterable(changes)); } /*package*/ String[] getContentTitles() { return myContentTitles; } /*package*/ MergeSession getMergeSession() { return myMergeSession; } public void resetState() { myMergeSession.restoreState(myInitialState); if (myMetadataMergeSession != null) { myMetadataMergeSession.restoreState(myMetadataInitialState); } rebuildLater(); } /*package*/ static String generateUnresolvedChangesText(int totalChanges, int conflictingChanges) { if (conflictingChanges != 0) { String text = NameUtil.formatNumericalString(conflictingChanges, "conficting change"); if (totalChanges == conflictingChanges) { return text; } else { return text + " of " + totalChanges + " total"; } } else { if (totalChanges == 0) { return "All changes resolved"; } else { return NameUtil.formatNumericalString(totalChanges, " change"); } } } private class MyGoToNeighbourRootActions extends GoToNeighbourRootActions.GoToByTree { public MyGoToNeighbourRootActions() { super(myMergeTree); } @Nullable @Override protected SNodeId getCurrentNodeId() { return getCurrentRoot(); } @Override public void setCurrentNodeId(@Nullable SNodeId nodeId) { setCurrentRoot(nodeId); } } private class MergeModelsTree extends DiffModelTree { private MergeModelsTree() { addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent event) { } }); } @Override protected Iterable<BaseAction> getRootActions() { MergeModelsPanel md = MergeModelsPanel.this; return Arrays.<BaseAction>asList(AcceptYoursTheirs.yoursInstance(md), AcceptYoursTheirs.theirsInstance(md)); } @Override protected void updateRootCustomPresentation(@NotNull DiffModelTree.RootTreeNode rootTreeNode) { final MergeSession session = (rootTreeNode.getRootId() == null ? myMetadataMergeSession : myMergeSession); List<ModelChange> changes = Sequence.fromIterable(((rootTreeNode.getRootId() == null ? myMetadataMergeSession.getAllChanges() : myMergeSession.getChangesForRoot(rootTreeNode.getRootId())))).where(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange ch) { return !(session.isChangeResolved(ch)); } }).toListSequence(); int conflictedCount = ListSequence.fromList(changes).where(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange ch) { return Sequence.fromIterable(session.getConflictedWith(ch)).isNotEmpty(); } }).count(); int nonConflictedCount = ListSequence.fromList(changes).count() - conflictedCount; ChangeType compositeChangeType = null; rootTreeNode.setTooltipText(generateUnresolvedChangesText(ListSequence.fromList(changes).count(), conflictedCount)); if (conflictedCount != 0) { compositeChangeType = ChangeType.CONFLICTED; rootTreeNode.setAdditionalText("with conflicts"); } else { if (nonConflictedCount == 0) { if (rootTreeNode.getRootId() != null && myMergeSession.getResultModel().getNode(rootTreeNode.getRootId()) == null) { rootTreeNode.setTextStyle(SimpleTextAttributes.STYLE_STRIKEOUT); } else { rootTreeNode.setAdditionalText(null); } } else { compositeChangeType = ChangeType.CHANGE; if (ListSequence.fromList(changes).all(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange ch) { return ch instanceof AddRootChange || ch instanceof DeleteRootChange; } })) { compositeChangeType = ListSequence.fromList(changes).first().getType(); } int myChangesCount = ListSequence.fromList(changes).where(new IWhereFilter<ModelChange>() { public boolean accept(ModelChange ch) { return session.isMyChange(ch); } }).count(); if (myChangesCount == nonConflictedCount) { rootTreeNode.setAdditionalText("local"); } else if (myChangesCount == 0) { rootTreeNode.setAdditionalText("remote"); } else { rootTreeNode.setAdditionalText("both modified"); } } } rootTreeNode.setColor((compositeChangeType == null ? null : ChangeColors.getForTree(compositeChangeType))); } @Override protected Iterable<SModel> getModels() { return Arrays.asList(myMergeSession.getBaseModel(), myMergeSession.getMyModel(), myMergeSession.getRepositoryModel()); } @Override protected Iterable<SNodeId> getAffectedRoots() { return myMergeSession.getAffectedRoots(); } @Override protected boolean isMultipleRootNames() { return true; } @Override protected void onUnselect() { resetCurrentRoot(); } @Override protected void onSelectRoot(@Nullable SNodeId rootId) { changeCurrentRoot(rootId); } } private static <T> T as_ktyr7l_a0a0a1a0a0a0a0a0qb(Object o, Class<T> type) { return (type.isInstance(o) ? (T) o : null); } }