blob: 9a68d63527aebd2591de343a3317e1cbc17c1f57 [file] [log] [blame]
/*
* 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.conflict.ConflictAction;
import org.jetbrains.idea.svn.conflict.ConflictReason;
import org.jetbrains.idea.svn.conflict.ConflictVersion;
import org.jetbrains.idea.svn.conflict.TreeConflictDescription;
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.wc.SVNRevision;
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(TreeConflictDescription d1, TreeConflictDescription 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(ConflictVersion v1, ConflictVersion 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(TreeConflictDescription 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(TreeConflictDescription description) {
return description.getSourceLeftVersion() != null && description.getSourceRightVersion() != null &&
! Comparing.equal(description.getSourceLeftVersion().getPath(), description.getSourceRightVersion().getPath());
}
private ConflictSidePresentation createSide(ConflictVersion 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(TreeConflictDescription description,
JPanel main,
GridBagConstraints gb,
BeforeAfter<ConflictSidePresentation> ba, boolean directory) {
if (description == null) return;
JLabel descriptionLbl = new JLabel(description.toPresentableString());
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(TreeConflictDescription 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 TreeConflictDescription 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<TreeConflictDescription>(myVcs.getProject(), "Accepting theirs for: " + filePath(paths.myMainPath),
BackgroundFromStartOption.getInstance(), Collections.singletonList(description),
true) {
@Override
protected void process(TreeConflictDescription 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 TreeConflictDescription description) {
FilePath mainPath;
FilePath additionalPath = null;
if (myChange.isMoved() || myChange.isRenamed()) {
if (ConflictAction.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 TreeConflictDescription 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<TreeConflictDescription>(myVcs.getProject(), "Accepting yours for: " + filePath(paths.myMainPath),
BackgroundFromStartOption.getInstance(), Collections.singletonList(description),
true) {
@Override
protected void process(TreeConflictDescription 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 TreeConflictDescription description) {
if (isDifferentURLs(description)) {
return null;
}
// my edit, theirs move or delete
if (ConflictAction.EDIT.equals(description.getConflictAction()) && description.getSourceLeftVersion() != null &&
ConflictReason.DELETED.equals(description.getConflictReason()) && (myChange.isMoved() || myChange.isRenamed()) &&
myCommittedRevision != null) {
if (myPath.isDirectory() == description.getSourceRightVersion().isDirectory()) {
return createMergeTheirsForFile(description);
}
}
return null;
}
private ActionListener createMergeTheirsForFile(final TreeConflictDescription 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(TreeConflictDescription 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,
ConflictVersion leftVersion, final String name, boolean directory) {
final String leftPresentation = leftVersion == null ? name + ": (" + (directory ? "directory" : "file") +
(myChange.getBeforeRevision() == null ? ") added" : ") unversioned") :
name + ": " + FileUtil.toSystemIndependentName(ConflictVersion.toPresentableString(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 ConflictVersion myVersion;
private AbstractConflictSide(Project project, ConflictVersion 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, ConflictVersion 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(), version.isDirectory());
}
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;
}
}
}