| /******************************************************************************* |
| * 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.*; |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.team.core.RepositoryProvider; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.core.subscribers.*; |
| import org.eclipse.team.core.synchronize.SyncInfo; |
| import org.eclipse.team.core.synchronize.SyncInfoFilter; |
| import org.eclipse.team.core.variants.*; |
| import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot; |
| import org.eclipse.team.internal.ccvs.core.resources.RemoteFile; |
| import org.eclipse.team.internal.ccvs.core.syncinfo.CVSResourceVariantTree; |
| import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo; |
| import org.eclipse.team.internal.ccvs.core.util.Util; |
| |
| /** |
| * 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, ISubscriberChangeListener { |
| |
| private final class MergeBaseTree extends CVSResourceVariantTree { |
| // The merge synchronizer has been kept so that those upgrading |
| // from 3.0 M8 to 3.0 M9 so not lose there ongoing merge state |
| private PersistantResourceVariantByteStore mergedSynchronizer; |
| private MergeBaseTree(ResourceVariantByteStore cache, CVSTag tag, boolean cacheFileContentsHint, String syncKeyPrefix) { |
| super(cache, tag, cacheFileContentsHint); |
| mergedSynchronizer = new PersistantResourceVariantByteStore(new QualifiedName(SYNC_KEY_QUALIFIER, syncKeyPrefix + "0merged")); //$NON-NLS-1$ |
| } |
| public IResource[] refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException { |
| // Only refresh the base of a resource once as it should not change |
| List unrefreshed = new ArrayList(); |
| for (int i = 0; i < resources.length; i++) { |
| IResource resource = resources[i]; |
| if (!hasResourceVariant(resource)) { |
| unrefreshed.add(resource); |
| } |
| } |
| if (unrefreshed.isEmpty()) { |
| monitor.done(); |
| return new IResource[0]; |
| } |
| IResource[] refreshed = super.refresh((IResource[]) unrefreshed.toArray(new IResource[unrefreshed.size()]), depth, monitor); |
| return refreshed; |
| } |
| public IResourceVariant getResourceVariant(IResource resource) throws TeamException { |
| // Use the merged bytes for the base if there are some |
| byte[] mergedBytes = mergedSynchronizer.getBytes(resource); |
| if (mergedBytes != null) { |
| byte[] parentBytes = getByteStore().getBytes(resource.getParent()); |
| if (parentBytes != null) { |
| return RemoteFile.fromBytes(resource, mergedBytes, parentBytes); |
| } |
| } |
| return super.getResourceVariant(resource); |
| } |
| |
| /** |
| * Mark the resource as merged by making it's base equal the remote |
| */ |
| public void merged(IResource resource, byte[] remoteBytes) throws TeamException { |
| if (remoteBytes == null) { |
| getByteStore().deleteBytes(resource); |
| } else { |
| getByteStore().setBytes(resource, remoteBytes); |
| } |
| } |
| |
| /** |
| * Return true if the remote has already been merged |
| * (i.e. the base equals the remote). |
| */ |
| public boolean isMerged(IResource resource, byte[] remoteBytes) throws TeamException { |
| byte[] mergedBytes = getByteStore().getBytes(resource); |
| return Util.equals(mergedBytes, remoteBytes); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.internal.ccvs.core.syncinfo.CVSResourceVariantTree#dispose() |
| */ |
| public void dispose() { |
| mergedSynchronizer.dispose(); |
| super.dispose(); |
| } |
| } |
| |
| public static final String ID = "org.eclipse.team.cvs.ui.cvsmerge-participant"; //$NON-NLS-1$ |
| public static final String ID_MODAL = "org.eclipse.team.cvs.ui.cvsmerge-participant-modal"; //$NON-NLS-1$ |
| private static final String UNIQUE_ID_PREFIX = "merge-"; //$NON-NLS-1$ |
| |
| private CVSTag start, end; |
| private List roots; |
| private CVSResourceVariantTree remoteTree; |
| private MergeBaseTree baseTree; |
| |
| public CVSMergeSubscriber(IResource[] roots, CVSTag start, CVSTag end) { |
| this(getUniqueId(), roots, start, end); |
| } |
| |
| private static QualifiedName getUniqueId() { |
| String uniqueId = Long.toString(System.currentTimeMillis()); |
| return new QualifiedName(ID, "CVS" + UNIQUE_ID_PREFIX + uniqueId); //$NON-NLS-1$ |
| } |
| |
| public CVSMergeSubscriber(QualifiedName id, IResource[] roots, CVSTag start, CVSTag end) { |
| super(id, Policy.bind("CVSMergeSubscriber.2", start.getName(), end.getName()), Policy.bind("CVSMergeSubscriber.4")); //$NON-NLS-1$ //$NON-NLS-2$ |
| 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(); |
| PersistantResourceVariantByteStore remoteSynchronizer = new PersistantResourceVariantByteStore(new QualifiedName(SYNC_KEY_QUALIFIER, syncKeyPrefix + end.getName())); |
| remoteTree = new CVSResourceVariantTree(remoteSynchronizer, getEndTag(), getCacheFileContentsHint()) { |
| public IResource[] refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException { |
| // Override refresh to compare file contents |
| monitor.beginTask(null, 100); |
| try { |
| IResource[] refreshed = super.refresh(resources, depth, monitor); |
| compareWithRemote(refreshed, Policy.subMonitorFor(monitor, 50)); |
| return refreshed; |
| } finally { |
| monitor.done(); |
| } |
| } |
| }; |
| PersistantResourceVariantByteStore baseSynchronizer = new PersistantResourceVariantByteStore(new QualifiedName(SYNC_KEY_QUALIFIER, syncKeyPrefix + start.getName())); |
| baseTree = new MergeBaseTree(baseSynchronizer, getStartTag(), getCacheFileContentsHint(), syncKeyPrefix); |
| |
| ResourcesPlugin.getWorkspace().addResourceChangeListener(this); |
| CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber().addListener(this); |
| } |
| |
| protected SyncInfo getSyncInfo(IResource local, IResourceVariant base, IResourceVariant remote) throws TeamException { |
| CVSMergeSyncInfo info = new CVSMergeSyncInfo(local, base, remote, this); |
| info.init(); |
| return info; |
| } |
| |
| public void merged(IResource[] resources) throws TeamException { |
| for (int i = 0; i < resources.length; i++) { |
| IResource resource = resources[i]; |
| internalMerged(resource); |
| } |
| fireTeamResourceChange(SubscriberChangeEvent.asSyncChangedDeltas(this, resources)); |
| } |
| |
| private void internalMerged(IResource resource) throws TeamException { |
| byte[] remoteBytes = getRemoteByteStore().getBytes(resource); |
| baseTree.merged(resource, remoteBytes); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.sync.TeamSubscriber#cancel() |
| */ |
| public void cancel() { |
| ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); |
| remoteTree.dispose(); |
| baseTree.dispose(); |
| } |
| |
| /* (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.core.sync.TeamSubscriber#isSupervised(org.eclipse.core.resources.IResource) |
| */ |
| public boolean isSupervised(IResource resource) throws TeamException { |
| return getBaseTree().hasResourceVariant(resource) || getRemoteTree().hasResourceVariant(resource); |
| } |
| |
| public CVSTag getStartTag() { |
| return start; |
| } |
| |
| public CVSTag getEndTag() { |
| return end; |
| } |
| |
| /* |
| * 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()); |
| } |
| } |
| |
| /** |
| * Return whether the given resource has been merged with its |
| * corresponding remote. |
| * @param resource the local resource |
| * @return boolean |
| * @throws TeamException |
| */ |
| public boolean isMerged(IResource resource) throws TeamException { |
| byte[] remoteBytes = getRemoteByteStore().getBytes(resource); |
| return baseTree.isMerged(resource, remoteBytes); |
| } |
| |
| /* |
| * 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 subscriberResourceChanged(ISubscriberChangeEvent[] deltas) { |
| for (int i = 0; i < deltas.length; i++) { |
| ISubscriberChangeEvent delta = deltas[i]; |
| switch(delta.getFlags()) { |
| case ISubscriberChangeEvent.ROOT_REMOVED: |
| IResource resource = delta.getResource(); |
| if(roots.remove(resource)) { |
| fireTeamResourceChange(new ISubscriberChangeEvent[] {delta}); |
| } |
| break; |
| } |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.internal.ccvs.core.CVSSyncTreeSubscriber#getBaseSynchronizationCache() |
| */ |
| protected IResourceVariantTree getBaseTree() { |
| return baseTree; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.internal.ccvs.core.CVSSyncTreeSubscriber#getRemoteSynchronizationCache() |
| */ |
| protected IResourceVariantTree getRemoteTree() { |
| return remoteTree; |
| } |
| |
| protected boolean getCacheFileContentsHint() { |
| return true; |
| } |
| |
| /* |
| * Mark as merged any local resources whose contents match that of the remote resource. |
| */ |
| private void compareWithRemote(IResource[] refreshed, IProgressMonitor monitor) throws CVSException, TeamException { |
| // For any remote changes, if the revision differs from the local, compare the contents. |
| if (refreshed.length == 0) return; |
| SyncInfoFilter.ContentComparisonSyncInfoFilter contentFilter = |
| new SyncInfoFilter.ContentComparisonSyncInfoFilter(); |
| monitor.beginTask(null, refreshed.length * 100); |
| for (int i = 0; i < refreshed.length; i++) { |
| IResource resource = refreshed[i]; |
| if (resource.getType() == IResource.FILE) { |
| ICVSFile local = CVSWorkspaceRoot.getCVSFileFor((IFile)resource); |
| byte[] localBytes = local.getSyncBytes(); |
| byte[] remoteBytes = getRemoteByteStore().getBytes(resource); |
| if (remoteBytes != null |
| && localBytes != null |
| && local.exists() |
| && !ResourceSyncInfo.getRevision(remoteBytes).equals(ResourceSyncInfo.getRevision(localBytes)) |
| && contentFilter.select(getSyncInfo(resource), Policy.subMonitorFor(monitor, 100))) { |
| // The contents are equals so mark the file as merged |
| internalMerged(resource); |
| } |
| } |
| } |
| monitor.done(); |
| } |
| |
| |
| private PersistantResourceVariantByteStore getRemoteByteStore() { |
| return (PersistantResourceVariantByteStore)((CVSResourceVariantTree)getRemoteTree()).getByteStore(); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| public boolean equals(Object other) { |
| if(this == other) return true; |
| if(! (other instanceof CVSMergeSubscriber)) return false; |
| CVSMergeSubscriber s = (CVSMergeSubscriber)other; |
| return getEndTag().equals(s.getEndTag()) && |
| getStartTag().equals(s.getStartTag()) && rootsEqual(s); |
| } |
| } |