blob: 3a4cfe0168e29ba4f3b3eec4ae52b8c0c8cfdd1c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.team.internal.ccvs.ui.sync;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.sync.ILocalSyncElement;
import org.eclipse.team.core.sync.IRemoteSyncElement;
import org.eclipse.team.internal.ccvs.core.CVSException;
import org.eclipse.team.internal.ccvs.core.ICVSResource;
import org.eclipse.team.internal.ccvs.core.resources.CVSRemoteSyncElement;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.ui.AvoidableMessageDialog;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.ccvs.ui.ICVSUIConstants;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.ui.sync.CatchupReleaseViewer;
import org.eclipse.team.internal.ui.sync.ChangedTeamContainer;
import org.eclipse.team.internal.ui.sync.ITeamNode;
import org.eclipse.team.internal.ui.sync.SyncCompareInput;
import org.eclipse.team.internal.ui.sync.SyncSet;
import org.eclipse.team.internal.ui.sync.TeamFile;
public class CVSSyncCompareInput extends SyncCompareInput {
private IResource[] resources;
private boolean onlyOutgoing = false;
public CVSSyncCompareInput(IResource[] resources) {
this(resources, false);
}
protected CVSSyncCompareInput(IResource[] resources, int granularity) {
super(granularity);
this.resources = getNonOverlapping(resources);
}
public CVSSyncCompareInput(IResource[] resources, boolean onlyOutgoing) {
super(CVSUIPlugin.getPlugin().getPreferenceStore().getBoolean(ICVSUIConstants.PREF_CONSIDER_CONTENTS) ? ILocalSyncElement.GRANULARITY_CONTENTS : ILocalSyncElement.GRANULARITY_TIMESTAMP);
this.onlyOutgoing = onlyOutgoing;
this.resources = getNonOverlapping(resources);
}
/**
* Method getNonOverlapping ensures that a resource is not covered more than once.
* @param resources
* @return IResource[]
*/
private IResource[] getNonOverlapping(IResource[] resources) {
// Sort the resources so the shortest paths are first
List sorted = new ArrayList();
sorted.addAll(Arrays.asList(resources));
Collections.sort(sorted, new Comparator() {
public int compare(Object arg0, Object arg1) {
IResource resource0 = (IResource) arg0;
IResource resource1 = (IResource) arg1;
return resource0.getFullPath().segmentCount() - resource1.getFullPath().segmentCount();
}
public boolean equals(Object arg0) {
return false;
}
});
// Collect all non-overlapping resources
List coveredPaths = new ArrayList();
for (Iterator iter = sorted.iterator(); iter.hasNext();) {
IResource resource = (IResource) iter.next();
IPath resourceFullPath = resource.getFullPath();
boolean covered = false;
for (Iterator it = coveredPaths.iterator(); it.hasNext();) {
IPath path = (IPath) it.next();
if(path.isPrefixOf(resourceFullPath)) {
covered = true;
}
}
if (covered) {
// if the resource is covered by a parent, remove it
iter.remove();
} else {
// if the resource is a non-covered folder, add it to the covered paths
if (resource.getType() == IResource.FOLDER) {
coveredPaths.add(resource.getFullPath());
}
}
}
return (IResource[]) sorted.toArray(new IResource[sorted.size()]);
}
/**
* Overridden to create a custom DiffTreeViewer in the top left pane of the CompareProvider.
*
* Subclasses must create and return a new CatchupReleaseViewer, and set the viewer
* using setViewer().
*/
public Viewer createDiffViewer(Composite parent) {
CatchupReleaseViewer catchupReleaseViewer = new CVSCatchupReleaseViewer(parent, this);
setViewer(catchupReleaseViewer);
// catchupReleaseViewer.getTree().addMouseMoveListener(new MouseMoveListener() {
// /**
// * @see MouseMoveListener#mouseMove(MouseEvent)
// */
// public void mouseMove(MouseEvent e) {
// final Tree tree = (Tree)e.widget;
// TreeItem item = tree.getItem(new Point(e.x, e.y));
// final TeamFile file;
// if (item != null) {
// // Hack: this is the only way to get an item from the tree viewer
// Object o = item.getData();
// if (o instanceof TeamFile) {
// file = (TeamFile)o;
// } else file = null;
// } else file = null;
//
// // avoid redundant updates -- identity test is good enough here
// if (file == previousTeamFile) return;
// previousTeamFile = file;
// getShell().getDisplay().asyncExec(new Runnable() {
// public void run() {
// updateToolTip(tree, file);
// }
// });
// }
// });
return catchupReleaseViewer;
}
// protected void updateToolTip(Tree tree, TeamFile file) {
// String newText = null;
// if (file != null && file.getChangeDirection() != ITeamNode.OUTGOING) {
// IRemoteSyncElement element = file.getMergeResource().getSyncElement();
// final ICVSRemoteFile remoteFile = (ICVSRemoteFile)element.getRemote();
// final ILogEntry[] logEntry = new ILogEntry[1];
// if (remoteFile != null) {
// try {
// CVSUIPlugin.runWithProgress(getViewer().getTree().getShell(), true /*cancelable*/,
// new IRunnableWithProgress() {
// public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
// try {
// logEntry[0] = remoteFile.getLogEntry(monitor);
// } catch (TeamException ex) {
// throw new InvocationTargetException(ex);
// }
// }
// });
// } catch (InterruptedException ex) {
// // ignore cancellation
// } catch (InvocationTargetException ex) {
// // ignore the exception
// }
// }
// if (logEntry[0] != null) {
// newText = logEntry[0].getComment();
// }
// }
// if (tree.isDisposed()) return;
// String oldText = tree.getToolTipText();
// if (newText == oldText || newText != null && newText.equals(oldText)) return;
// tree.setToolTipText(newText);
// }
protected IRemoteSyncElement[] createSyncElements(IProgressMonitor monitor) throws TeamException {
// Ensure that the projects for all resources being synchronized exist
// Note: this could happen on a refresh view after a synced project was deleted.
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
if (!resource.getProject().exists()) {
throw new CVSException(Policy.bind("CVSSyncCompareInput.projectDeleted", resource.getProject().getName())); //$NON-NLS-1$
}
}
monitor.beginTask(null, 1000 + (onlyOutgoing?10:0));
IResource[] resourcesToSync;
if (onlyOutgoing) {
List filteredResources = Arrays.asList(resources);
filteredResources = filterOutgoingChangesSet(filteredResources, Policy.subMonitorFor(monitor, 10));
resourcesToSync = (IResource[]) filteredResources.toArray(new IResource[filteredResources.size()]);
} else {
resourcesToSync = resources;
}
return buildSyncTrees(resourcesToSync, Policy.subMonitorFor(monitor, 1000));
}
private IRemoteSyncElement[] buildSyncTrees(IResource[] resourcesToSync, IProgressMonitor monitor)throws TeamException {
IRemoteSyncElement[] trees = new IRemoteSyncElement[resourcesToSync.length];
int work = 1000 * resourcesToSync.length;
monitor.beginTask(null, work);
try {
for (int i = 0; i < trees.length; i++) {
trees[i] = CVSWorkspaceRoot.getRemoteSyncTree(resourcesToSync[i], null, Policy.subMonitorFor(monitor, 1000));
}
} finally {
monitor.done();
}
return trees;
}
protected void updateView() {
// Update the view
if (getDiffRoot().hasChildren()) {
getViewer().refresh();
} else {
getViewer().setInput(null);
}
// Update the status line
updateStatusLine();
}
/**
* Overridden to mark the source as merged.
*/
protected void compareInputChanged(ICompareInput source) {
super.compareInputChanged(source);
updateView();
// prompt user with warning
Shell shell = getShell();
if(shell != null) {
// prompt
if(source instanceof TeamFile) {
TeamFile file = (TeamFile)source;
int direction = file.getChangeDirection();
int type = file.getChangeType();
if(direction == IRemoteSyncElement.INCOMING ||
direction == IRemoteSyncElement.CONFLICTING) {
promptForConfirmMerge(getShell());
}
}
}
}
/*
* Helper method to get cvs elements from the selection in the sync editor input
*/
public static CVSRemoteSyncElement getSyncElementFrom(Object node) {
CVSRemoteSyncElement element = null;
if (node instanceof TeamFile) {
element = (CVSRemoteSyncElement)((TeamFile)node).getMergeResource().getSyncElement();
} else if (node instanceof ChangedTeamContainer) {
element = (CVSRemoteSyncElement)((ChangedTeamContainer)node).getMergeResource().getSyncElement();
}
return element;
}
/*
* Returns the resources in this input.
*/
public IResource[] getResources() {
return resources;
}
/*
* Inform user that when changes are merged in the sync view that confirm
* merge should be called to finish the merge.
*/
private void promptForConfirmMerge(final Shell shell) {
final IPreferenceStore store = CVSUIPlugin.getPlugin().getPreferenceStore();
if(!store.getBoolean(ICVSUIConstants.PREF_PROMPT_ON_SAVING_IN_SYNC)) {
return;
};
shell.getDisplay().syncExec(new Runnable() {
public void run() {
AvoidableMessageDialog dialog = new AvoidableMessageDialog(
shell,
Policy.bind("CVSSyncCompareInput.confirmMergeMessageTitle"), //$NON-NLS-1$
null, // accept the default window icon
Policy.bind("CVSSyncCompareInput.confirmMergeMessage"), //$NON-NLS-1$
MessageDialog.INFORMATION,
new String[] {IDialogConstants.OK_LABEL},
0);
dialog.open();
if(dialog.isDontShowAgain()) {
store.setValue(ICVSUIConstants.PREF_PROMPT_ON_SAVING_IN_SYNC, false);
}
}
});
}
/**
* Wrap the input preparation in a CVS session run so open sessions will be reused and
* file contents under the same remote root folder will be fetched using the same connection.
*
* Also run with refresh prompting if one of the resources is out of sync with the local
* file system.
*/
public Object prepareInput(IProgressMonitor pm) throws InterruptedException, InvocationTargetException {
final Object[] result = new Object[] { null };
CVSUIPlugin.runWithRefresh(getShell(), resources, new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
result[0] = CVSSyncCompareInput.super.prepareInput(monitor);
}
}, pm);
if (hasDifferences(result[0])) {
return result[0];
} else {
return null;
}
}
/**
* The given object has differences if it is an IDiffElement with a change
* or it is an IDiffContainer that contains differences.
*
* @param object
* @return boolean
*/
private boolean hasDifferences(Object object) {
if (object instanceof IDiffElement) {
IDiffElement element = (IDiffElement)object;
if (element.getKind() != Differencer.NO_CHANGE) return true;
if (object instanceof IDiffContainer) {
IDiffContainer container = (IDiffContainer) object;
if (container.hasChildren()) {
IDiffElement[] children = container.getChildren();
for (int i = 0; i < children.length; i++) {
IDiffElement child = children[i];
if (hasDifferences(child)) return true;
}
}
}
}
return false;
}
private boolean hasIncomingChanges(ChangedTeamContainer container) {
IDiffElement[] children = container.getChildren();
for (int i = 0; i < children.length; i++) {
IDiffElement element = children[i];
int direction = element.getKind() & Differencer.DIRECTION_MASK;
if (direction == ITeamNode.CONFLICTING || direction == ITeamNode.INCOMING) {
return true;
}
if (element instanceof ChangedTeamContainer) {
boolean hasIncomingChanges = hasIncomingChanges((ChangedTeamContainer)element);
if (hasIncomingChanges) return true;
}
}
return false;
}
/*
* Method copied from TeamAction. It should be put in a common place
*/
protected Map getProviderMapping(IResource[] resources) {
Map result = new HashMap();
for (int i = 0; i < resources.length; i++) {
RepositoryProvider provider = RepositoryProvider.getProvider(resources[i].getProject());
List list = (List)result.get(provider);
if (list == null) {
list = new ArrayList();
result.put(provider, list);
}
list.add(resources[i]);
}
return result;
}
protected SyncSet getSyncSet(IStructuredSelection selection) {
return new CVSSyncSet(selection);
}
/*
* Return the resources from the original list that are modified.
*/
private List filterOutgoingChangesSet(List resources, IProgressMonitor monitor) throws CVSException {
try {
monitor.beginTask(null, 100 * resources.size());
monitor.subTask(Policy.bind("CVSSyncCompareInput.filteringOutgoingChanges")); //$NON-NLS-1$
List result = new ArrayList();
for (Iterator iter = resources.iterator(); iter.hasNext();) {
IResource resource = (IResource) iter.next();
ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
if (!cvsResource.isIgnored() && cvsResource.isModified(Policy.subMonitorFor(monitor, 100)))
result.add(resource);
}
return result;
} finally {
monitor.done();
}
}
}