org.jetbrains.idea.svn.treeConflict.TreeConflictRefreshablePanel.java Source code

Java tutorial

Introduction

Here is the source code for org.jetbrains.idea.svn.treeConflict.TreeConflictRefreshablePanel.java

Source

/*
 * Copyright 2000-2012 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.jetbrains.idea.svn.treeConflict;

import com.intellij.openapi.CompositeDisposable;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.BackgroundTaskQueue;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FilePathImpl;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.AbstractRefreshablePanel;
import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangesUtil;
import com.intellij.openapi.vcs.history.*;
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
import com.intellij.util.BeforeAfter;
import com.intellij.util.containers.Convertor;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.VcsBackgroundTask;
import gnu.trove.TLongArrayList;
import org.jetbrains.idea.svn.ConflictedSvnChange;
import org.jetbrains.idea.svn.SvnRevisionNumber;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.history.SvnHistoryProvider;
import org.jetbrains.idea.svn.history.SvnHistorySession;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.internal.wc.SVNConflictVersion;
import org.tmatesoft.svn.core.internal.wc.SVNTreeConflictUtil;
import org.tmatesoft.svn.core.wc.SVNConflictAction;
import org.tmatesoft.svn.core.wc.SVNConflictReason;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: Irina.Chernushina
 * Date: 4/25/12
 * Time: 5:33 PM
 */
public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel {
    public static final String TITLE = "Resolve tree conflict";
    private final ConflictedSvnChange myChange;
    private final SvnVcs myVcs;
    private SvnRevisionNumber myCommittedRevision;
    private FilePath myPath;
    private final CompositeDisposable myChildDisposables = new CompositeDisposable();
    private final TLongArrayList myRightRevisionsList;

    public TreeConflictRefreshablePanel(Project project, String loadingTitle, BackgroundTaskQueue queue,
            Change change) {
        super(project, loadingTitle, queue);
        myVcs = SvnVcs.getInstance(project);
        assert change instanceof ConflictedSvnChange;
        myChange = (ConflictedSvnChange) change;
        myPath = ChangesUtil.getFilePath(myChange);
        myRightRevisionsList = new TLongArrayList();
    }

    @Override
    public boolean isStillValid(final Change change) {
        return change.isTreeConflict() && change instanceof ConflictedSvnChange && descriptionsEqual(
                ((ConflictedSvnChange) change).getBeforeDescription(), myChange.getBeforeDescription());
    }

    @Override
    public boolean refreshDataSynch() {
        return true;
    }

    private static boolean descriptionsEqual(SVNTreeConflictDescription d1, SVNTreeConflictDescription d2) {
        if (d1.isPropertyConflict() != d2.isPropertyConflict())
            return false;
        if (d1.isTextConflict() != d2.isTextConflict())
            return false;
        if (d1.isTreeConflict() != d2.isTreeConflict())
            return false;

        if (!d1.getOperation().equals(d2.getOperation()))
            return false;
        if (!d1.getConflictAction().equals(d2.getConflictAction()))
            return false;
        if (!Comparing.equal(d1.getConflictReason(), d2.getConflictReason()))
            return false;
        if (!Comparing.equal(d1.getPath(), d2.getPath()))
            return false;
        if (!Comparing.equal(d1.getNodeKind(), d2.getNodeKind()))
            return false;
        if (!compareConflictVersion(d1.getSourceLeftVersion(), d2.getSourceLeftVersion()))
            return false;
        if (!compareConflictVersion(d1.getSourceRightVersion(), d2.getSourceRightVersion()))
            return false;
        return true;
    }

    private static boolean compareConflictVersion(SVNConflictVersion v1, SVNConflictVersion v2) {
        if (v1 == null && v2 == null)
            return true;
        if (v1 == null || v2 == null)
            return false;
        if (!v1.getKind().equals(v2.getKind()))
            return false;
        if (!v1.getPath().equals(v2.getPath()))
            return false;
        if (v1.getPegRevision() != v2.getPegRevision())
            return false;
        if (!Comparing.equal(v1.getRepositoryRoot(), v2.getRepositoryRoot()))
            return false;
        return true;
    }

    @Override
    protected void refreshPresentation() {
    }

    @Override
    protected Object loadImpl() throws VcsException {
        return new BeforeAfter<BeforeAfter<ConflictSidePresentation>>(
                processDescription(myChange.getBeforeDescription()),
                processDescription(myChange.getAfterDescription()));
    }

    private BeforeAfter<ConflictSidePresentation> processDescription(SVNTreeConflictDescription description)
            throws VcsException {
        if (description == null)
            return null;
        if (myChange.getBeforeRevision() != null) {
            myCommittedRevision = (SvnRevisionNumber) SvnHistorySession.getCurrentCommittedRevision(myVcs,
                    myChange.getBeforeRevision() != null ? myChange.getBeforeRevision().getFile().getIOFile()
                            : myPath.getIOFile());
        }
        boolean differentURLs = isDifferentURLs(description);

        ConflictSidePresentation leftSide = null;
        ConflictSidePresentation rightSide = null;
        try {
            if (differentURLs) {
                leftSide = createSide(description.getSourceLeftVersion(), null, true);
                rightSide = createSide(description.getSourceRightVersion(), null, false);
                leftSide.load();
                rightSide.load();
            } else {
                //only one side
                leftSide = EmptyConflictSide.getInstance();
                final SVNRevision pegFromLeft;
                if (description.getSourceLeftVersion() == null) {
                    pegFromLeft = null;
                } else {
                    long committed = description.getSourceLeftVersion().getPegRevision();
                    if (myCommittedRevision != null && myCommittedRevision.getRevision().getNumber() < committed
                            && myCommittedRevision.getRevision().isValid()) {
                        committed = myCommittedRevision.getRevision().getNumber();
                    }
                    pegFromLeft = SVNRevision.create(committed);
                }
                rightSide = createSide(description.getSourceRightVersion(), pegFromLeft, false);
                rightSide.load();
                return new BeforeAfter<ConflictSidePresentation>(leftSide, rightSide);
            }
        } catch (SVNException e) {
            throw new VcsException(e);
        } finally {
            if (leftSide != null) {
                myChildDisposables.add(leftSide);
            }
            if (rightSide != null) {
                myChildDisposables.add(rightSide);
            }
        }

        return new BeforeAfter<ConflictSidePresentation>(leftSide, rightSide);
    }

    private static boolean isDifferentURLs(SVNTreeConflictDescription description) {
        return description.getSourceLeftVersion() != null && description.getSourceRightVersion() != null
                && !Comparing.equal(description.getSourceLeftVersion().getPath(),
                        description.getSourceRightVersion().getPath());
    }

    private ConflictSidePresentation createSide(SVNConflictVersion version, final SVNRevision untilThisOther,
            final boolean isLeft) throws VcsException {
        if (version == null)
            return EmptyConflictSide.getInstance();
        if (myChange.getBeforeRevision() != null && myCommittedRevision != null) {
            SvnRevisionNumber number = myCommittedRevision;
            if (isLeft && number.getRevision().isValid()
                    && number.getRevision().getNumber() == version.getPegRevision()) {
                return EmptyConflictSide.getInstance();
            }
        }
        HistoryConflictSide side = new HistoryConflictSide(myVcs, version, untilThisOther);
        if (untilThisOther != null && !isLeft) {
            side.setListToReportLoaded(myRightRevisionsList);
        }
        return side;
    }

    @Override
    protected JPanel dataToPresentation(Object o) {
        final BeforeAfter<BeforeAfter<ConflictSidePresentation>> ba = (BeforeAfter<BeforeAfter<ConflictSidePresentation>>) o;
        final JPanel wrapper = new JPanel(new BorderLayout());
        final JPanel main = new JPanel(new GridBagLayout());

        final GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST,
                GridBagConstraints.HORIZONTAL, new Insets(1, 1, 1, 1), 0, 0);
        final String pathComment = myCommittedRevision == null ? ""
                : " (current: " + myChange.getBeforeRevision().getRevisionNumber().asString() + ", committed: "
                        + myCommittedRevision.asString() + ")";
        final JLabel name = new JLabel(myPath.getName() + pathComment);
        name.setFont(name.getFont().deriveFont(Font.BOLD));
        gb.insets.top = 5;
        main.add(name, gb);
        ++gb.gridy;
        gb.insets.top = 10;
        appendDescription(myChange.getBeforeDescription(), main, gb, ba.getBefore(), myPath.isDirectory());
        appendDescription(myChange.getAfterDescription(), main, gb, ba.getAfter(), myPath.isDirectory());
        wrapper.add(main, BorderLayout.NORTH);
        return wrapper;
    }

    private void appendDescription(SVNTreeConflictDescription description, JPanel main, GridBagConstraints gb,
            BeforeAfter<ConflictSidePresentation> ba, boolean directory) {
        if (description == null)
            return;
        JLabel descriptionLbl = new JLabel(SVNTreeConflictUtil.getHumanReadableConflictDescription(description));
        descriptionLbl.setForeground(Color.red);
        main.add(descriptionLbl, gb);
        ++gb.gridy;
        //buttons
        gb.insets.top = 0;
        addResolveButtons(description, main, gb);

        addSide(main, gb, ba.getBefore(), description.getSourceLeftVersion(), "Left", directory);
        addSide(main, gb, ba.getAfter(), description.getSourceRightVersion(), "Right", directory);
    }

    private void addResolveButtons(SVNTreeConflictDescription description, JPanel main, GridBagConstraints gb) {
        final FlowLayout flowLayout = new FlowLayout(FlowLayout.LEFT, 5, 5);
        JPanel wrapper = new JPanel(flowLayout);
        final JButton both = new JButton("Both");
        final JButton merge = new JButton("Merge");
        final JButton left = new JButton("Accept Yours");
        final JButton right = new JButton("Accept Theirs");
        enableAndSetListener(createBoth(description), both);
        enableAndSetListener(createMerge(description), merge);
        enableAndSetListener(createLeft(description), left);
        enableAndSetListener(createRight(description), right);
        //wrapper.add(both);
        if (merge.isEnabled()) {
            wrapper.add(merge);
        }
        wrapper.add(left);
        wrapper.add(right);
        gb.insets.left = -4;
        main.add(wrapper, gb);
        gb.insets.left = 1;
        ++gb.gridy;
    }

    private ActionListener createRight(final SVNTreeConflictDescription description) {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int ok = Messages.showOkCancelDialog(myVcs.getProject(),
                        "Accept theirs for " + filePath(myPath) + "?", TITLE, Messages.getQuestionIcon());
                if (Messages.OK != ok)
                    return;
                FileDocumentManager.getInstance().saveAllDocuments();
                final Paths paths = getPaths(description);
                ProgressManager.getInstance().run(new VcsBackgroundTask<SVNTreeConflictDescription>(
                        myVcs.getProject(), "Accepting theirs for: " + filePath(paths.myMainPath),
                        BackgroundFromStartOption.getInstance(), Collections.singletonList(description), true) {
                    @Override
                    protected void process(SVNTreeConflictDescription d) throws VcsException {
                        new SvnTreeConflictResolver(myVcs, paths.myMainPath, myCommittedRevision,
                                paths.myAdditionalPath).resolveSelectTheirsFull(d);
                    }

                    @Override
                    public void onSuccess() {
                        super.onSuccess();
                        if (executedOk()) {
                            VcsBalloonProblemNotifier.showOverChangesView(myProject,
                                    "Theirs accepted for " + filePath(paths.myMainPath), MessageType.INFO);
                        }
                    }
                });
            }
        };
    }

    private Paths getPaths(final SVNTreeConflictDescription description) {
        FilePath mainPath = new FilePathImpl(description.getPath(),
                SVNNodeKind.DIR.equals(description.getNodeKind()));
        FilePath additionalPath = null;
        if (myChange.isMoved() || myChange.isRenamed()) {
            if (SVNConflictAction.ADD.equals(description.getConflictAction())) {
                mainPath = myChange.getAfterRevision().getFile();
                additionalPath = myChange.getBeforeRevision().getFile();
            } else {
                mainPath = myChange.getBeforeRevision().getFile();
                additionalPath = myChange.getAfterRevision().getFile();
            }
        } else {
            mainPath = myChange.getBeforeRevision() != null ? myChange.getBeforeRevision().getFile()
                    : myChange.getAfterRevision().getFile();
        }
        return new Paths(mainPath, additionalPath);
    }

    private static class Paths {
        public final FilePath myMainPath;
        public final FilePath myAdditionalPath;

        private Paths(FilePath mainPath, FilePath additionalPath) {
            myMainPath = mainPath;
            myAdditionalPath = additionalPath;
        }
    }

    private ActionListener createLeft(final SVNTreeConflictDescription description) {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int ok = Messages.showOkCancelDialog(myVcs.getProject(),
                        "Accept yours for " + filePath(myPath) + "?", TITLE, Messages.getQuestionIcon());
                if (Messages.OK != ok)
                    return;
                FileDocumentManager.getInstance().saveAllDocuments();
                final Paths paths = getPaths(description);
                ProgressManager.getInstance().run(new VcsBackgroundTask<SVNTreeConflictDescription>(
                        myVcs.getProject(), "Accepting yours for: " + filePath(paths.myMainPath),
                        BackgroundFromStartOption.getInstance(), Collections.singletonList(description), true) {
                    @Override
                    protected void process(SVNTreeConflictDescription d) throws VcsException {
                        new SvnTreeConflictResolver(myVcs, paths.myMainPath, myCommittedRevision,
                                paths.myAdditionalPath).resolveSelectMineFull(d);
                    }

                    @Override
                    public void onSuccess() {
                        super.onSuccess();
                        if (executedOk()) {
                            VcsBalloonProblemNotifier.showOverChangesView(myProject,
                                    "Yours accepted for " + filePath(paths.myMainPath), MessageType.INFO);
                        }
                    }
                });
            }
        };
    }

    private ActionListener createMerge(final SVNTreeConflictDescription description) {
        if (isDifferentURLs(description)) {
            return null;
        }
        // my edit, theirs move or delete
        if (SVNConflictAction.EDIT.equals(description.getConflictAction())
                && description.getSourceLeftVersion() != null
                && SVNConflictReason.DELETED.equals(description.getConflictReason())
                && (myChange.isMoved() || myChange.isRenamed()) && myCommittedRevision != null) {
            if (myPath.isDirectory() == SVNNodeKind.DIR.equals(description.getSourceRightVersion().getKind())) {
                return createMergeTheirsForFile(description);
            }
        }
        return null;
    }

    private ActionListener createMergeTheirsForFile(final SVNTreeConflictDescription description) {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new MergeFromTheirsResolver(myVcs, description, myChange, myCommittedRevision).execute();
            }
        };
    }

    public static String filePath(FilePath newFilePath) {
        return newFilePath.getName() + " (" + newFilePath.getParentPath().getPath() + ")";
    }

    private static ActionListener createBoth(SVNTreeConflictDescription description) {
        return null;
    }

    private static void enableAndSetListener(final ActionListener al, final JButton b) {
        if (al == null) {
            b.setEnabled(false);
        } else {
            b.addActionListener(al);
        }
    }

    private void addSide(JPanel main, GridBagConstraints gb, ConflictSidePresentation before,
            SVNConflictVersion leftVersion, final String name, boolean directory) {
        final String leftPresentation = leftVersion == null
                ? name + ": (" + (directory ? "directory" : "file")
                        + (myChange.getBeforeRevision() == null ? ") added" : ") unversioned")
                : name + ": " + FileUtil
                        .toSystemIndependentName(SVNTreeConflictUtil.getHumanReadableConflictVersion(leftVersion));
        gb.insets.top = 10;
        main.add(new JLabel(leftPresentation), gb);
        ++gb.gridy;
        gb.insets.top = 0;

        if (before != null) {
            JPanel panel = before.createPanel();
            if (panel != null) {
                //gb.fill = GridBagConstraints.HORIZONTAL;
                main.add(panel, gb);
                //gb.fill = GridBagConstraints.NONE;
                ++gb.gridy;
            }
        }
    }

    @Override
    protected void disposeImpl() {
        Disposer.dispose(myChildDisposables);
    }

    @Override
    public void away() {
    }

    private interface ConflictSidePresentation extends Disposable {
        JPanel createPanel();

        void load() throws SVNException, VcsException;
    }

    private static class EmptyConflictSide implements ConflictSidePresentation {
        private static final EmptyConflictSide ourInstance = new EmptyConflictSide();

        public static EmptyConflictSide getInstance() {
            return ourInstance;
        }

        @Override
        public JPanel createPanel() {
            return null;
        }

        @Override
        public void dispose() {
        }

        @Override
        public void load() throws SVNException {
        }
    }

    private abstract static class AbstractConflictSide<T>
            implements ConflictSidePresentation, Convertor<T, VcsRevisionNumber> {
        protected final Project myProject;
        protected final SVNConflictVersion myVersion;

        private AbstractConflictSide(Project project, SVNConflictVersion version) {
            myProject = project;
            myVersion = version;
        }
    }

    private static class HistoryConflictSide extends AbstractConflictSide<VcsFileRevision> {
        public static final int LIMIT = 10;
        private final VcsAppendableHistoryPartnerAdapter mySessionAdapter;
        private final SvnHistoryProvider myProvider;
        private final FilePath myPath;
        private final SvnVcs myVcs;
        private final SVNRevision myPeg;
        private FileHistoryPanelImpl myFileHistoryPanel;
        private TLongArrayList myListToReportLoaded;

        private HistoryConflictSide(SvnVcs vcs, SVNConflictVersion version, final SVNRevision peg)
                throws VcsException {
            super(vcs.getProject(), version);
            myVcs = vcs;
            myPeg = peg;
            try {
                myPath = FilePathImpl.createNonLocal(
                        version.getRepositoryRoot()
                                .appendPath(FileUtil.toSystemIndependentName(version.getPath()), true).toString(),
                        SVNNodeKind.DIR.equals(version.getKind()));
            } catch (SVNException e) {
                throw new VcsException(e);
            }

            mySessionAdapter = new VcsAppendableHistoryPartnerAdapter();
            /*mySessionAdapter.reportCreatedEmptySession(new SvnHistorySession(myVcs, Collections.<VcsFileRevision>emptyList(),
              myPath, SvnUtil.checkRepositoryVersion15(myVcs, version.getPath()), null, true));*/
            myProvider = (SvnHistoryProvider) myVcs.getVcsHistoryProvider();
        }

        public void setListToReportLoaded(TLongArrayList listToReportLoaded) {
            myListToReportLoaded = listToReportLoaded;
        }

        @Override
        public VcsRevisionNumber convert(VcsFileRevision o) {
            return o.getRevisionNumber();
        }

        @Override
        public void load() throws SVNException, VcsException {
            SVNRevision from = SVNRevision.create(myVersion.getPegRevision());
            myProvider.reportAppendableHistory(myPath, mySessionAdapter, from, myPeg, myPeg == null ? LIMIT : 0,
                    myPeg, true);
            VcsAbstractHistorySession session = mySessionAdapter.getSession();
            if (myListToReportLoaded != null && session != null) {
                List<VcsFileRevision> list = session.getRevisionList();
                for (VcsFileRevision revision : list) {
                    myListToReportLoaded
                            .add(((SvnRevisionNumber) revision.getRevisionNumber()).getRevision().getNumber());
                }
            }
        }

        @Override
        public void dispose() {
            if (myFileHistoryPanel != null) {
                myFileHistoryPanel.dispose();
            }
        }

        @Override
        public JPanel createPanel() {
            VcsAbstractHistorySession session = mySessionAdapter.getSession();
            if (session == null)
                return EmptyConflictSide.getInstance().createPanel();
            List<VcsFileRevision> list = session.getRevisionList();
            if (list.isEmpty()) {
                return EmptyConflictSide.getInstance().createPanel();
            }
            VcsFileRevision last = null;
            if (!list.isEmpty() && myPeg == null && list.size() == LIMIT
                    || myPeg != null && myPeg.getNumber() > 0 && myPeg.equals(
                            ((SvnRevisionNumber) list.get(list.size() - 1).getRevisionNumber()).getRevision())) {
                last = list.remove(list.size() - 1);
            }
            myFileHistoryPanel = new FileHistoryPanelImpl(myVcs, myPath, session, myProvider, null,
                    new FileHistoryRefresherI() {
                        @Override
                        public void run(boolean isRefresh, boolean canUseCache) {
                            //we will not refresh
                        }

                        @Override
                        public boolean isFirstTime() {
                            return false;
                        }
                    }, true);
            myFileHistoryPanel.setBottomRevisionForShowDiff(last);
            myFileHistoryPanel.setBorder(BorderFactory.createLineBorder(UIUtil.getBorderColor()));
            return myFileHistoryPanel;
        }
    }
}