blob: 588c34f9a190047cd2514712f11d30011171f870 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.team.internal.ccvs.ui.subscriber;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.synchronize.SyncInfo;
import org.eclipse.team.core.synchronize.SyncInfoSet;
import org.eclipse.team.core.variants.IResourceVariant;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.PruneFolderVisitor;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.resources.EclipseSynchronizer;
import org.eclipse.team.internal.ccvs.ui.CVSUIMessages;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.ui.TeamUIPlugin;
import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration;
import org.eclipse.team.ui.synchronize.SynchronizeModelOperation;
public abstract class CVSSubscriberOperation extends SynchronizeModelOperation {
protected CVSSubscriberOperation(ISynchronizePageConfiguration configuration, IDiffElement[] elements) {
super(configuration, elements);
}
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
// Divide the sync info by project
final Map projectSyncInfos = getProjectSyncInfoSetMap();
monitor.beginTask(null, projectSyncInfos.size() * 100);
for (Iterator iter = projectSyncInfos.keySet().iterator(); iter.hasNext(); ) {
final IProject project = (IProject) iter.next();
run(projectSyncInfos, project, monitor);
}
monitor.done();
}
/**
* Run the operation for the sync infos from the given project. By default, a lock
* is acquired on the project.
* @param projectSyncInfos the project syncInfos
* @param project the project
* @param monitor a progress monitor
* @throws InvocationTargetException
*/
protected void run(final Map projectSyncInfos, final IProject project, IProgressMonitor monitor) throws InvocationTargetException {
try {
// Pass the scheduling rule to the synchronizer so that sync change events
// and cache commits to disk are batched
EclipseSynchronizer.getInstance().run(
project,
monitor1 -> {
try {
CVSSubscriberOperation.this.runWithProjectRule(project,
(SyncInfoSet) projectSyncInfos.get(project), monitor1);
} catch (TeamException e) {
throw CVSException.wrapException(e);
}
}, Policy.subMonitorFor(monitor, 100));
} catch (TeamException e) {
throw new InvocationTargetException(e);
}
}
/**
* Run the operation on the sync info in the given set. The sync info will be all
* from the same project. Also, a scheduling rule on the project will be
* held when this method is invoked.
* @param project the project that contaisn the sync info.
* @param set the sync info set
* @param monitor a progress monitor
*/
protected abstract void runWithProjectRule(IProject project, SyncInfoSet set, IProgressMonitor monitor) throws TeamException;
/*
* Indicate that the resource is out of sync if the sync state is not IN_SYNC
* or if the local doesn't exist but the remote does.
*/
protected boolean isOutOfSync(SyncInfo resource) {
if (resource == null) return false;
return (!(resource.getKind() == 0) ||
(! resource.getLocal().exists() && resource.getRemote() != null));
}
protected void makeInSync(SyncInfo[] folders, IProgressMonitor monitor) throws TeamException {
// If a node has a parent that is an incoming folder creation, we have to
// create that folder locally and set its sync info before we can get the
// node itself. We must do this for all incoming folder creations (recursively)
// in the case where there are multiple levels of incoming folder creations.
monitor.beginTask(null, folders.length);
for (int i = 0; i < folders.length; i++) {
SyncInfo resource = folders[i];
makeInSync(resource);
monitor.worked(1);
}
monitor.done();
}
protected boolean makeInSync(SyncInfo info) throws TeamException {
if (isOutOfSync(info)) {
SyncInfo parent = getParent(info);
if (parent == null) {
if (info.getLocal().getType() == IResource.ROOT) {
// ROOT should be null
return true;
} else {
// No other ancestors should be null. Log the problem.
CVSUIPlugin.log(IStatus.WARNING, NLS.bind(CVSUIMessages.CVSSubscriberAction_0, new String[] { info.getLocal().getFullPath().toString() }), null);
return false;
}
} else {
if (!makeInSync(parent)) {
// The failed makeInSync will log any errors
return false;
}
}
if (info instanceof CVSSyncInfo) {
CVSSyncInfo cvsInfo= (CVSSyncInfo) info;
IStatus status = cvsInfo.makeInSync();
if (status.getSeverity() == IStatus.ERROR) {
logError(status);
return false;
}
return true;
}
return false;
} else {
return true;
}
}
protected void makeOutgoing(SyncInfo[] folders, IProgressMonitor monitor) throws TeamException {
// If a node has a parent that is an incoming folder creation, we have to
// create that folder locally and set its sync info before we can get the
// node itself. We must do this for all incoming folder creations (recursively)
// in the case where there are multiple levels of incoming folder creations.
monitor.beginTask(null, 100 * folders.length);
for (int i = 0; i < folders.length; i++) {
SyncInfo info = folders[i];
makeOutgoing(info, Policy.subMonitorFor(monitor, 100));
}
monitor.done();
}
private void makeOutgoing(SyncInfo info, IProgressMonitor monitor) throws TeamException {
if (info == null) return;
if (info instanceof CVSSyncInfo) {
CVSSyncInfo cvsInfo= (CVSSyncInfo) info;
IStatus status = cvsInfo.makeOutgoing(monitor);
if (status.getSeverity() == IStatus.ERROR) {
logError(status);
}
}
}
/**
* Log an error associated with an operation.
* @param status
*/
protected void logError(IStatus status) {
CVSUIPlugin.log(status);
}
/**
* Handle the exception by showing an error dialog to the user.
* Sync actions seem to need to be sync-execed to work
* @param t
*/
protected void handle(Exception t) {
CVSUIPlugin.openError(getShell(), getErrorTitle(), null, t, CVSUIPlugin.PERFORM_SYNC_EXEC | CVSUIPlugin.LOG_NONTEAM_EXCEPTIONS);
}
/**
* Return the error title that will appear in any error dialogs shown to the user
* @return
*/
protected String getErrorTitle() {
return null;
}
@Override
protected boolean canRunAsJob() {
return true;
}
protected void pruneEmptyParents(SyncInfo[] nodes) throws CVSException {
// TODO: A more explicit tie in to the pruning mechanism would be prefereable.
// i.e. I don't like referencing the option and visitor directly
if (!CVSProviderPlugin.getPlugin().getPruneEmptyDirectories()) return;
ICVSResource[] cvsResources = new ICVSResource[nodes.length];
for (int i = 0; i < cvsResources.length; i++) {
cvsResources[i] = CVSWorkspaceRoot.getCVSResourceFor(nodes[i].getLocal());
}
new PruneFolderVisitor().visit(
CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot()),
cvsResources);
}
public CVSSyncInfo getCVSSyncInfo(SyncInfo info) {
if (info instanceof CVSSyncInfo) {
return (CVSSyncInfo)info;
}
return null;
}
protected SyncInfo getParent(SyncInfo info) throws TeamException {
return ((CVSSyncInfo)info).getSubscriber().getSyncInfo(info.getLocal().getParent());
}
protected IResource[] getIResourcesFrom(SyncInfo[] nodes) {
List resources = new ArrayList(nodes.length);
for (int i = 0; i < nodes.length; i++) {
resources.add(nodes[i].getLocal());
}
return (IResource[]) resources.toArray(new IResource[resources.size()]);
}
/**
* Prompt to overwrite those resources that could not be safely updated
* Note: This method is designed to be overridden by test cases.
*
* @return whether to perform the overwrite
*/
protected boolean promptForOverwrite(final SyncInfoSet syncSet) {
final int[] result = new int[] {Window.CANCEL};
TeamUIPlugin.getStandardDisplay().syncExec(() -> {
UpdateDialog dialog = new UpdateDialog(getShell(), syncSet);
result[0] = dialog.open();
});
return (result[0] == UpdateDialog.YES);
}
/**
* Make the contents of the local resource match that of the remote
* without modifying the sync info of the local resource.
* If called on a new folder, the sync info will be copied.
*/
protected void makeRemoteLocal(SyncInfo info, IProgressMonitor monitor) throws TeamException {
IResourceVariant remote = info.getRemote();
IResource local = info.getLocal();
try {
if(remote==null) {
if (local.exists()) {
local.delete(IResource.KEEP_HISTORY, monitor);
}
} else {
if(remote.isContainer()) {
ensureContainerExists(info);
} else {
monitor.beginTask(null, 200);
try {
IFile localFile = (IFile)local;
if(local.exists()) {
localFile.setContents(remote.getStorage(Policy.subMonitorFor(monitor, 100)).getContents(), false /*don't force*/, true /*keep history*/, Policy.subMonitorFor(monitor, 100));
} else {
ensureContainerExists(getParent(info));
localFile.create(remote.getStorage(Policy.subMonitorFor(monitor, 100)).getContents(), false /*don't force*/, Policy.subMonitorFor(monitor, 100));
}
} finally {
monitor.done();
}
}
}
} catch(CoreException e) {
IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, CVSUIMessages.UpdateMergeActionProblems_merging_remote_resources_into_workspace_1,e, local);
throw new CVSException(status);
}
}
private boolean ensureContainerExists(SyncInfo info) throws TeamException {
IResource local = info.getLocal();
// make sure that the parent exists
if (!local.exists()) {
if (!ensureContainerExists(getParent(info))) {
return false;
}
}
// make sure that the folder sync info is set;
if (isOutOfSync(info)) {
if (info instanceof CVSSyncInfo) {
CVSSyncInfo cvsInfo = (CVSSyncInfo)info;
IStatus status = cvsInfo.makeInSync();
if (status.getSeverity() == IStatus.ERROR) {
logError(status);
return false;
}
}
}
// create the folder if it doesn't exist
ICVSFolder cvsFolder = CVSWorkspaceRoot.getCVSFolderFor((IContainer)local);
if (!cvsFolder.exists()) {
cvsFolder.mkdir();
}
return true;
}
/*
* Divide the sync info for the operation by project
*/
private Map getProjectSyncInfoSetMap() {
Map map = new HashMap();
SyncInfoSet all = getSyncInfoSet();
SyncInfo[] infos = all.getSyncInfos();
for (int i = 0; i < infos.length; i++) {
SyncInfo info = infos[i];
IProject project = info.getLocal().getProject();
SyncInfoSet set = (SyncInfoSet)map.get(project);
if (set == null) {
set = new SyncInfoSet();
map.put(project, set);
}
set.add(info);
}
return map;
}
}