blob: e62c0e7a256994c76f0259e8102a623aadb7acf4 [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.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.subscribers.ContentComparisonCriteria;
import org.eclipse.team.core.subscribers.ITeamResourceChangeListener;
import org.eclipse.team.core.subscribers.SyncInfo;
import org.eclipse.team.core.subscribers.TeamDelta;
import org.eclipse.team.core.subscribers.TeamProvider;
import org.eclipse.team.core.sync.IRemoteResource;
import org.eclipse.team.internal.ccvs.core.syncinfo.RemoteSynchronizer;
import org.eclipse.team.internal.ccvs.core.syncinfo.RemoteTagSynchronizer;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSynchronizer;
import org.eclipse.team.internal.core.SaveContext;
/**
* A CVSMergeSubscriber is responsible for maintaining the remote trees for a merge into
* the workspace. The remote trees represent the CVS revisions of the start and end
* points (version or branch) of the merge.
*
* This subscriber stores the remote handles in the resource tree sync info slot. When
* the merge is cancelled this sync info is cleared.
*
* A merge can persist between workbench sessions and thus can be used as an
* ongoing merge.
*
* TODO: Is the merge subscriber interested in workspace sync info changes?
* TODO: Do certain operations (e.g. replace with) invalidate a merge subscriber?
* TODO: How to ensure that sync info is flushed when merge roots are deleted?
*/
public class CVSMergeSubscriber extends CVSSyncTreeSubscriber implements IResourceChangeListener, ITeamResourceChangeListener {
public static final String UNIQUE_ID_PREFIX = "merge-";
private CVSTag start, end;
private List roots;
private RemoteTagSynchronizer remoteSynchronizer;
private RemoteSynchronizer mergedSynchronizer;
private RemoteTagSynchronizer baseSynchronizer;
private static final byte[] NO_REMOTE = new byte[0];
protected IResource[] refreshRemote(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException {
IResource[] remoteChanges = super.refreshRemote(resources, depth, monitor);
adjustMergedResources(remoteChanges);
return remoteChanges;
}
private void adjustMergedResources(IResource[] remoteChanges) throws CVSException {
for (int i = 0; i < remoteChanges.length; i++) {
IResource resource = remoteChanges[i];
mergedSynchronizer.removeSyncBytes(resource, IResource.DEPTH_ZERO, false /* not silent */);
}
}
private static QualifiedName getUniqueId() {
String uniqueId = Long.toString(System.currentTimeMillis());
return new QualifiedName(CVSSubscriberFactory.ID, UNIQUE_ID_PREFIX + uniqueId);
}
public CVSMergeSubscriber(IResource[] roots, CVSTag start, CVSTag end) {
this(getUniqueId(), roots, start, end);
}
public CVSMergeSubscriber(QualifiedName id, IResource[] roots, CVSTag start, CVSTag end) {
super(id, "CVS Merge: " + start.getName() + " to " + end.getName(), "CVS Merge");
this.start = start;
this.end = end;
this.roots = new ArrayList(Arrays.asList(roots));
initialize();
}
/* (non-Javadoc)
* @see org.eclipse.team.internal.ccvs.core.CVSWorkspaceSubscriber#initialize()
*/
private void initialize() {
QualifiedName id = getId();
String syncKeyPrefix = id.getLocalName();
remoteSynchronizer = new RemoteTagSynchronizer(syncKeyPrefix + end.getName(), end);
baseSynchronizer = new RemoteTagSynchronizer(syncKeyPrefix + start.getName(), start);
mergedSynchronizer = new RemoteSynchronizer(syncKeyPrefix + "0merged"); //$NON-NLS-1$
try {
setCurrentComparisonCriteria(ContentComparisonCriteria.ID_IGNORE_WS);
} catch (TeamException e) {
// use the default but log an exception because the content comparison should
// always be available.
CVSProviderPlugin.log(e);
}
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber().addListener(this);
}
protected SyncInfo getSyncInfo(IResource local, IRemoteResource base, IRemoteResource remote, IProgressMonitor monitor) throws TeamException {
return new CVSMergeSyncInfo(local, base, remote, this, monitor);
}
public void merged(IResource[] resources) throws CVSException {
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
byte[] remoteBytes = remoteSynchronizer.getSyncBytes(resource);
if (remoteBytes == null) {
// If there is no remote, use a place holder to indicate the resouce was merged
remoteBytes = NO_REMOTE;
}
mergedSynchronizer.setSyncBytes(resource, remoteBytes);
}
fireTeamResourceChange(TeamDelta.asSyncChangedDeltas(this, resources));
}
/* (non-Javadoc)
* @see org.eclipse.team.core.sync.TeamSubscriber#cancel()
*/
public void cancel() {
super.cancel();
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
TeamProvider.deregisterSubscriber(this);
remoteSynchronizer.dispose();
baseSynchronizer.dispose();
mergedSynchronizer.dispose();
}
/* (non-Javadoc)
* @see org.eclipse.team.core.sync.TeamSubscriber#isCancellable()
*/
public boolean isCancellable() {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.team.core.sync.TeamSubscriber#roots()
*/
public IResource[] roots() {
return (IResource[]) roots.toArray(new IResource[roots.size()]);
}
/* (non-Javadoc)
* @see org.eclipse.team.internal.ccvs.core.CVSSyncTreeSubscriber#getRemoteSynchronizer()
*/
protected ResourceSynchronizer getRemoteSynchronizer() {
return remoteSynchronizer;
}
/* (non-Javadoc)
* @see org.eclipse.team.internal.ccvs.core.CVSSyncTreeSubscriber#getBaseSynchronizer()
*/
protected ResourceSynchronizer getBaseSynchronizer() {
return baseSynchronizer;
}
/* (non-Javadoc)
* @see org.eclipse.team.core.sync.TeamSubscriber#isSupervised(org.eclipse.core.resources.IResource)
*/
public boolean isSupervised(IResource resource) throws TeamException {
return getBaseSynchronizer().getSyncBytes(resource) != null || getRemoteSynchronizer().getSyncBytes(resource) != null;
}
/* (non-Javadoc)
* @see org.eclipse.team.core.subscribers.TeamSubscriber#saveState(org.eclipse.team.internal.core.SaveContext)
*/
public SaveContext saveState() {
// start and end tags
SaveContext state = new SaveContext();
state.setName("merge");
state.putString("startTag", start.getName());
state.putInteger("startTagType", start.getType());
state.putString("endTag", end.getName());
state.putInteger("endTagType", end.getType());
// resources roots
SaveContext[] ctxRoots = new SaveContext[roots.size()];
int i = 0;
for (Iterator it = roots.iterator(); it.hasNext(); i++) {
IResource element = (IResource) it.next();
ctxRoots[i] = new SaveContext();
ctxRoots[i].setName("resource");
ctxRoots[i].putString("fullpath", element.getFullPath().toString());
}
state.setChildren(ctxRoots);
return state;
}
public static CVSMergeSubscriber restore(QualifiedName id, SaveContext saveContext) throws CVSException {
String name = saveContext.getName();
if(! name.equals("merge")) {
throw new CVSException("error restoring merge subscriber: " + name + " is an invalid save context.");
}
CVSTag start = new CVSTag(saveContext.getString("startTag"), saveContext.getInteger("startTagType"));
CVSTag end = new CVSTag(saveContext.getString("endTag"), saveContext.getInteger("endTagType"));
SaveContext[] ctxRoots = saveContext.getChildren();
if(ctxRoots == null || ctxRoots.length == 0) {
throw new CVSException("error restoring merge subscriber: there are no roots in the save context.");
}
List resources = new ArrayList();
for (int i = 0; i < ctxRoots.length; i++) {
SaveContext context = ctxRoots[i];
IPath path = new Path(context.getString("fullpath"));
IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path, true /* include phantoms */);
if(resource != null) {
resources.add(resource);
} else {
// log that a resource previously in the merge set is no longer in the workspace
CVSProviderPlugin.log(CVSStatus.INFO, "ignoring root resource not found in current workspace", null);
}
}
if(resources.isEmpty()) {
throw new CVSException("error restoring merge subscriber: there are no existing roots in the save context.");
}
IResource[] roots = (IResource[]) resources.toArray(new IResource[resources.size()]);
return new CVSMergeSubscriber(id, roots, start, end);
}
public CVSTag getStartTag() {
return start;
}
public CVSTag getEndTag() {
return end;
}
public boolean isReleaseSupported() {
// you can't release changes to a merge
return false;
}
/*
* What to do when a root resource for this merge changes?
* Deleted, Move, Copied
* Changed in a CVS way (tag changed, revision changed...)
* Contents changed by user
* @see IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
*/
public void resourceChanged(IResourceChangeEvent event) {
try {
IResourceDelta delta = event.getDelta();
if(delta != null) {
delta.accept(new IResourceDeltaVisitor() {
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
if (resource.getType()==IResource.PROJECT) {
IProject project = (IProject)resource;
if (!project.isAccessible()) {
return false;
}
if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
return false;
}
if (RepositoryProvider.getProvider(project, CVSProviderPlugin.getTypeId()) == null) {
return false;
}
}
if (roots.contains(resource)) {
if (delta.getKind() == IResourceDelta.REMOVED || delta.getKind() == IResourceDelta.MOVED_TO) {
cancel();
}
// stop visiting children
return false;
}
// keep visiting children
return true;
}
});
}
} catch (CoreException e) {
CVSProviderPlugin.log(e.getStatus());
}
}
public boolean isMerged(IResource resource) throws CVSException {
return mergedSynchronizer.getSyncBytes(resource) != null;
}
/*
* Currently only the workspace subscriber knows when a project has been deconfigured. We will listen for these events
* and remove the root then forward to merge subscriber listeners.
* (non-Javadoc)
* @see org.eclipse.team.core.subscribers.ITeamResourceChangeListener#teamResourceChanged(org.eclipse.team.core.subscribers.TeamDelta[])
*/
public void teamResourceChanged(TeamDelta[] deltas) {
for (int i = 0; i < deltas.length; i++) {
TeamDelta delta = deltas[i];
switch(delta.getFlags()) {
case TeamDelta.PROVIDER_DECONFIGURED:
IResource resource = delta.getResource();
if(roots.remove(resource)) {
fireTeamResourceChange(new TeamDelta[] {delta});
}
break;
}
}
}
}