diff options
57 files changed, 4213 insertions, 240 deletions
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoTree.java index 353830cc6..7e4ed3c28 100644 --- a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoTree.java +++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoTree.java @@ -13,6 +13,7 @@ package org.eclipse.team.core.synchronize; import java.util.*; import org.eclipse.core.resources.*; +import org.eclipse.core.resources.mapping.ResourceTraversal; import org.eclipse.core.runtime.*; import org.eclipse.team.internal.core.Messages; import org.eclipse.team.internal.core.TeamPlugin; @@ -46,6 +47,11 @@ public class SyncInfoTree extends SyncInfoSet { */ public SyncInfoTree(SyncInfo[] infos) { super(infos); + for (int i = 0; i < infos.length; i++) { + SyncInfo info = infos[i]; + IResource local = info.getLocal(); + addToParents(local, local); + } } /** @@ -350,4 +356,29 @@ public class SyncInfoTree extends SyncInfoSet { return (IResource[]) children.toArray(new IResource[children.size()]); } + /** + * Return the sync info contained in this set that are contained + * in the given traversals. + * @param traversals the traversals + * @return the sync info contained in this set that are contained + * in the given traversals + * @since 3.2 + */ + public SyncInfo[] getSyncInfos(ResourceTraversal[] traversals) { + SyncInfoSet set = new SyncInfoSet(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + IResource[] resources = traversal.getResources(); + for (int j = 0; j < resources.length; j++) { + IResource resource = resources[j]; + SyncInfo[] infos = getSyncInfos(resource, traversal.getDepth()); + for (int k = 0; k < infos.length; k++) { + SyncInfo info = infos[k]; + set.add(info); + } + } + } + return set.getSyncInfos(); + } + } diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/variants/CachedResourceVariant.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/variants/CachedResourceVariant.java index 383885a4a..5d5a72e01 100644 --- a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/variants/CachedResourceVariant.java +++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/variants/CachedResourceVariant.java @@ -151,7 +151,7 @@ public abstract class CachedResourceVariant extends PlatformObject implements IR * <p> * This method is not intended to be overridden by clients. */ - protected boolean isContentsCached() { + public boolean isContentsCached() { if (isContainer() || !isHandleCached()) { return false; } diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java index 9cb32f7d3..fedd69bbf 100644 --- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java +++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java @@ -240,5 +240,12 @@ public class ResourceVariantCache { public String getName() { return name; } + + /* + * Method used for testing only + */ + public ResourceVariantCacheEntry[] getEntries() { + return (ResourceVariantCacheEntry[]) cacheEntries.values().toArray(new ResourceVariantCacheEntry[cacheEntries.size()]); + } } diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberResourceMappingContext.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberResourceMappingContext.java index 6eb6582dc..d1cbbbb0a 100644 --- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberResourceMappingContext.java +++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberResourceMappingContext.java @@ -10,17 +10,29 @@ *******************************************************************************/ package org.eclipse.team.internal.core.subscribers; -import java.util.*; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; -import org.eclipse.core.resources.*; -import org.eclipse.core.resources.mapping.*; -import org.eclipse.core.runtime.*; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.mapping.RemoteResourceMappingContext; +import org.eclipse.core.resources.mapping.ResourceTraversal; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; import org.eclipse.team.core.TeamException; import org.eclipse.team.core.subscribers.Subscriber; import org.eclipse.team.core.synchronize.SyncInfo; import org.eclipse.team.core.synchronize.SyncInfoFilter; import org.eclipse.team.core.variants.IResourceVariant; -import org.eclipse.team.internal.core.*; +import org.eclipse.team.internal.core.Messages; +import org.eclipse.team.internal.core.Policy; +import org.eclipse.team.internal.core.TeamPlugin; /** * A resource mapping context that provides the client access to the remote state @@ -28,6 +40,8 @@ import org.eclipse.team.internal.core.*; * to determine whether the local contents differ from the remote contents. * This allows the context to be used for different operations (check-in, * update and replace). + * + * TODO: Do we want explicit support for differentiating a commit from an update? * @since 3.1 */ public class SubscriberResourceMappingContext extends RemoteResourceMappingContext { @@ -36,14 +50,15 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte private final SyncInfoFilter contentDiffFilter; // Lists used to keep track of resources that have been refreshed - Set shallowRefresh = new HashSet(); - Set deepRefresh = new HashSet(); + private Set shallowRefresh = new HashSet(); + private Set deepRefresh = new HashSet(); + private boolean autoRefresh; /** * Return a resource mapping context suitable for a replace operations. * @return a resource mapping context suitable for a replace operations */ - public static ResourceMappingContext getReplaceContext(Subscriber subscriber) { + public static RemoteResourceMappingContext getReplaceContext(Subscriber subscriber) { return new SubscriberResourceMappingContext(subscriber, new SyncInfoFilter() { public boolean select(SyncInfo info, IProgressMonitor monitor) { if (info != null) { @@ -53,8 +68,7 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte } return false; } - - }); + }, true); } /** @@ -63,7 +77,7 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte * server to update the local workspace resources. * @return a resource mapping context suitable for a update operations */ - public static ResourceMappingContext getUpdateContext(Subscriber subscriber) { + public static RemoteResourceMappingContext getUpdateContext(Subscriber subscriber) { return new SubscriberResourceMappingContext(subscriber, new SyncInfoFilter() { public boolean select(SyncInfo info, IProgressMonitor monitor) { if (info != null) { @@ -73,8 +87,7 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte } return false; } - - }); + }, true); } /** @@ -83,7 +96,7 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte * server from the local workspace resources, typically creating a new version of the resource. * @return a resource mapping context suitable for a check-in operations */ - public static ResourceMappingContext getCheckInContext(Subscriber subscriber) { + public static RemoteResourceMappingContext getCheckInContext(Subscriber subscriber) { return new SubscriberResourceMappingContext(subscriber, new SyncInfoFilter() { public boolean select(SyncInfo info, IProgressMonitor monitor) { if (info != null) { @@ -93,8 +106,7 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte } return false; } - - }); + }, true); } /** @@ -103,7 +115,7 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte * that differ. * @return a resource mapping context suitable for compare operations */ - public static ResourceMappingContext getCompareContext(Subscriber subscriber) { + public static RemoteResourceMappingContext getCompareContext(Subscriber subscriber) { return new SubscriberResourceMappingContext(subscriber, new SyncInfoFilter() { public boolean select(SyncInfo info, IProgressMonitor monitor) { if (info != null) { @@ -111,8 +123,7 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte } return false; } - - }); + }, true); } /** @@ -121,30 +132,43 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte * @param contentDiffFilter filter that is used to determine if the remote contents differ * from the local contents */ - public SubscriberResourceMappingContext(Subscriber subscriber, SyncInfoFilter contentDiffFilter) { + public SubscriberResourceMappingContext(Subscriber subscriber, SyncInfoFilter contentDiffFilter, boolean autoRefresh) { this.subscriber = subscriber; this.contentDiffFilter = contentDiffFilter; + this.autoRefresh = autoRefresh; } - /* (non-Javadoc) - * @see org.eclipse.core.resources.mapping.ResourceMappingContext#contentDiffers(org.eclipse.core.resources.IFile, org.eclipse.core.runtime.IProgressMonitor) + /* (non-Javadoc) + * @see org.eclipse.core.internal.resources.mapping.RemoteResourceMappingContext#hasRemoteChange(org.eclipse.core.resources.IResource, org.eclipse.core.runtime.IProgressMonitor) */ - public final boolean contentDiffers(IFile file, IProgressMonitor monitor) throws CoreException { + public final boolean hasRemoteChange(IResource resource, IProgressMonitor monitor) throws CoreException { try { monitor.beginTask(null, 100); - ensureRefreshed(file, IResource.DEPTH_ZERO, NONE, Policy.subMonitorFor(monitor, 10)); - SyncInfo syncInfo = subscriber.getSyncInfo(file); - validateRemote(file, syncInfo); - return syncInfo != null && contentDiffFilter.select(syncInfo, Policy.subMonitorFor(monitor, 90)); + ensureRefreshed(resource, IResource.DEPTH_ONE, NONE, monitor); + SyncInfo syncInfo = subscriber.getSyncInfo(resource); + validateRemote(resource, syncInfo); + if (syncInfo == null) return false; + int direction = SyncInfo.getDirection(syncInfo.getKind()); + return direction == SyncInfo.OUTGOING || direction == SyncInfo.CONFLICTING; } finally { monitor.done(); } } + + /* (non-Javadoc) + * @see org.eclipse.core.internal.resources.mapping.RemoteResourceMappingContext#hasLocalChange(org.eclipse.core.resources.IResource, org.eclipse.core.runtime.IProgressMonitor) + */ + public boolean hasLocalChange(IResource resource, IProgressMonitor monitor) throws CoreException { + SyncInfo syncInfo = subscriber.getSyncInfo(resource); + if (syncInfo == null) return false; + int direction = SyncInfo.getDirection(syncInfo.getKind()); + return direction == SyncInfo.OUTGOING || direction == SyncInfo.CONFLICTING; + } /* (non-Javadoc) * @see org.eclipse.core.resources.mapping.ResourceMappingContext#fetchContents(org.eclipse.core.resources.IFile, org.eclipse.core.runtime.IProgressMonitor) */ - public final IStorage fetchContents(IFile file, IProgressMonitor monitor) throws CoreException { + public final IStorage fetchRemoteContents(IFile file, IProgressMonitor monitor) throws CoreException { try { monitor.beginTask(null, 100); ensureRefreshed(file, IResource.DEPTH_ZERO, FILE_CONTENTS_REQUIRED, Policy.subMonitorFor(monitor, 10)); @@ -158,6 +182,24 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte monitor.done(); } } + + /* (non-Javadoc) + * @see org.eclipse.core.internal.resources.mapping.RemoteResourceMappingContext#fetchBaseContents(org.eclipse.core.resources.IFile, org.eclipse.core.runtime.IProgressMonitor) + */ + public final IStorage fetchBaseContents(IFile file, IProgressMonitor monitor) throws CoreException { + try { + monitor.beginTask(null, 100); + ensureRefreshed(file, IResource.DEPTH_ZERO, FILE_CONTENTS_REQUIRED, Policy.subMonitorFor(monitor, 10)); + SyncInfo syncInfo = subscriber.getSyncInfo(file); + IResourceVariant base = validateBase(file, syncInfo); + if (base == null) { + return null; + } + return base.getStorage(Policy.subMonitorFor(monitor, 90)); + } finally { + monitor.done(); + } + } /* (non-Javadoc) * @see org.eclipse.core.resources.mapping.ResourceMappingContext#fetchMembers(org.eclipse.core.resources.IContainer, org.eclipse.core.runtime.IProgressMonitor) @@ -213,7 +255,7 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte * flag is passed. It is up to subclass to handle this. * @param resources the resources to be refreshed * @param depth the depth of the refresh - * @param flags the flags that indicate extra state that shoudl be fetched + * @param flags the flags that indicate extra state that should be fetched * @param monitor a progress monitor * @throws TeamException */ @@ -246,18 +288,20 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte * since the context has been created. */ private void ensureRefreshed(IResource resource, int depth, int flags, IProgressMonitor monitor) throws TeamException { - if (depth == IResource.DEPTH_INFINITE) { - // If the resource or a parent was refreshed deeply, no need to do it again - if (wasRefreshedDeeply(resource)) - return; - // if the resource is a file, a shallow refresh is enough - if (resource.getType() == IResource.FILE && wasRefreshedShallow(resource)) - return; - } else { - if (wasRefreshedShallow(resource)) - return; - } - refresh(new IResource[] { resource }, depth, flags, monitor); + if (autoRefresh) { + if (depth == IResource.DEPTH_INFINITE) { + // If the resource or a parent was refreshed deeply, no need to do it again + if (wasRefreshedDeeply(resource)) + return; + // if the resource is a file, a shallow refresh is enough + if (resource.getType() == IResource.FILE && wasRefreshedShallow(resource)) + return; + } else { + if (wasRefreshedShallow(resource)) + return; + } + refresh(new IResource[] { resource }, depth, flags, monitor); + } } /* @@ -294,12 +338,39 @@ public class SubscriberResourceMappingContext extends RemoteResourceMappingConte if (syncInfo == null) return null; IResourceVariant remote = syncInfo.getRemote(); if (remote == null) return null; - boolean containerExpected = resource.getType() != IResource.FILE; + return validateRemote(resource, remote); + } + + private IResourceVariant validateRemote(IResource resource, IResourceVariant remote) throws CoreException { + boolean containerExpected = resource.getType() != IResource.FILE; if (remote.isContainer() && !containerExpected) { throw new CoreException(new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.RESOURCE_WRONG_TYPE, Messages.SubscriberResourceMappingContext_0 + resource.getFullPath().toString(), null)); } else if (!remote.isContainer() && containerExpected) { throw new CoreException(new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.RESOURCE_WRONG_TYPE, Messages.SubscriberResourceMappingContext_1 + resource.getFullPath().toString(), null)); } return remote; + } + + /* + * Validate that the base resource is of the proper type and return the + * base resource if it is OK. A return of null indicates that there is no base. + */ + private IResourceVariant validateBase(IResource resource, SyncInfo syncInfo) throws CoreException { + if (syncInfo == null) return null; + IResourceVariant base = syncInfo.getBase(); + if (base == null) return null; + return validateRemote(resource, base); + } + + public void setAutoRefresh(boolean autoRefresh) { + this.autoRefresh = autoRefresh; } + + public boolean isThreeWay() { + return subscriber.getResourceComparator().isThreeWay(); + } + + public boolean contentDiffers(IFile file, IProgressMonitor monitor) throws CoreException { + return hasRemoteChange(file, monitor) || hasLocalChange(file, monitor); + } } diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSWorkspaceSubscriber.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSWorkspaceSubscriber.java index 4f4d35400..edc591bc3 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSWorkspaceSubscriber.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSWorkspaceSubscriber.java @@ -13,36 +13,18 @@ package org.eclipse.team.internal.ccvs.core; import java.util.ArrayList; import java.util.List; -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceVisitor; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; import org.eclipse.osgi.util.NLS; -import org.eclipse.team.core.ITeamStatus; -import org.eclipse.team.core.RepositoryProvider; -import org.eclipse.team.core.TeamException; -import org.eclipse.team.core.TeamStatus; +import org.eclipse.team.core.*; import org.eclipse.team.core.subscribers.ISubscriberChangeEvent; import org.eclipse.team.core.subscribers.SubscriberChangeEvent; 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.core.variants.IResourceVariantTree; -import org.eclipse.team.core.variants.PersistantResourceVariantByteStore; -import org.eclipse.team.core.variants.ResourceVariantByteStore; -import org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation; +import org.eclipse.team.core.variants.*; import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot; import org.eclipse.team.internal.ccvs.core.resources.EclipseSynchronizer; -import org.eclipse.team.internal.ccvs.core.resources.RemoteFolderTreeBuilder; -import org.eclipse.team.internal.ccvs.core.syncinfo.CVSBaseResourceVariantTree; -import org.eclipse.team.internal.ccvs.core.syncinfo.CVSDescendantResourceVariantByteStore; -import org.eclipse.team.internal.ccvs.core.syncinfo.CVSResourceVariantTree; +import org.eclipse.team.internal.ccvs.core.syncinfo.*; import org.eclipse.team.internal.ccvs.core.util.ResourceStateChangeListeners; /** @@ -277,13 +259,12 @@ public class CVSWorkspaceSubscriber extends CVSSyncTreeSubscriber implements IRe */ public void updateRemote(CVSTeamProvider provider, ICVSFolder folder, boolean recurse, IProgressMonitor monitor) throws TeamException { try { - monitor.beginTask(null, 100); + monitor.beginTask(null, IProgressMonitor.UNKNOWN); IResource resource = folder.getIResource(); if (resource != null) { - ICVSResource tree = RemoteFolderTreeBuilder.buildBaseTree( - (CVSRepositoryLocation)provider.getRemoteLocation(), - folder, - null, + ICVSResource tree = buildBaseTree( + resource, + false, Policy.subMonitorFor(monitor, 50)); setRemote(resource, (IResourceVariant)tree, Policy.subMonitorFor(monitor, 50)); } @@ -291,5 +272,23 @@ public class CVSWorkspaceSubscriber extends CVSSyncTreeSubscriber implements IRe monitor.done(); } } + + public ICVSRemoteResource buildBaseTree(IResource resource, boolean immutable, IProgressMonitor monitor) throws TeamException { + try { + monitor.beginTask(null, IProgressMonitor.UNKNOWN); + return ((CVSResourceVariantTree)getBaseTree()).buildTree(null, resource, immutable, monitor); + } finally { + monitor.done(); + } + } + + public ICVSRemoteResource buildRemoteTree(IResource resource, boolean immutable, IProgressMonitor monitor) throws TeamException { + try { + monitor.beginTask(null, IProgressMonitor.UNKNOWN); + return ((CVSResourceVariantTree)getRemoteTree()).buildTree(null, resource, immutable, monitor); + } finally { + monitor.done(); + } + } } diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFile.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFile.java index f6a7e5698..6f3a3e8c7 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFile.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFile.java @@ -132,7 +132,7 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { return newInfo.getBytes(); } - /* package */ RemoteFile(RemoteFolder parent, byte[] syncBytes) throws CVSException { + public RemoteFile(RemoteFolder parent, byte[] syncBytes) throws CVSException { this(parent, Update.STATE_NONE, syncBytes); } diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFolderTree.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFolderTree.java index 49230bbf7..b111ed527 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFolderTree.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFolderTree.java @@ -11,6 +11,7 @@ package org.eclipse.team.internal.ccvs.core.resources; +import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.team.internal.ccvs.core.CVSException; import org.eclipse.team.internal.ccvs.core.CVSTag; @@ -18,6 +19,9 @@ import org.eclipse.team.internal.ccvs.core.ICVSRemoteResource; import org.eclipse.team.internal.ccvs.core.ICVSRepositoryLocation; import org.eclipse.team.internal.ccvs.core.ICVSResource; import org.eclipse.team.internal.ccvs.core.ICVSResourceVisitor; +import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo; +import org.eclipse.team.internal.ccvs.core.util.Assert; +import org.eclipse.team.internal.ccvs.core.util.KnownRepositories; /** * Whereas the RemoteFolder class provides access to a remote hierarchy using @@ -26,12 +30,23 @@ import org.eclipse.team.internal.ccvs.core.ICVSResourceVisitor; */ public class RemoteFolderTree extends RemoteFolder { + public static RemoteFolderTree fromBytes(RemoteFolderTree parent, IResource local, byte[] bytes) throws CVSException { + Assert.isNotNull(bytes); + Assert.isTrue(local.getType() != IResource.FILE); + FolderSyncInfo syncInfo = FolderSyncInfo.getFolderSyncInfo(bytes); + return new RemoteFolderTree(parent, local.getName(), KnownRepositories.getInstance().getRepository(syncInfo.getRoot()), syncInfo.getRepository(), syncInfo.getTag(), syncInfo.getIsStatic()); + } + public RemoteFolderTree(RemoteFolder parent, ICVSRepositoryLocation repository, String repositoryRelativePath, CVSTag tag) { super(parent, repository, repositoryRelativePath, tag); } public RemoteFolderTree(RemoteFolder parent, String name, ICVSRepositoryLocation repository, String repositoryRelativePath, CVSTag tag) { - super(parent, name, repository, repositoryRelativePath, tag, false); + this(parent, name, repository, repositoryRelativePath, tag, false); + } + + public RemoteFolderTree(RemoteFolder parent, String name, ICVSRepositoryLocation repository, String repositoryRelativePath, CVSTag tag, boolean isStatic) { + super(parent, name, repository, repositoryRelativePath, tag, isStatic); } /* diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/CVSResourceVariantTree.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/CVSResourceVariantTree.java index 8478e5af9..bd1dcef47 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/CVSResourceVariantTree.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/CVSResourceVariantTree.java @@ -352,4 +352,40 @@ public class CVSResourceVariantTree extends ResourceVariantTree { } return false; } + + + public ICVSRemoteResource buildTree(RemoteFolderTree parent, IResource resource, boolean immutable, IProgressMonitor monitor) throws TeamException { + + Policy.checkCanceled(monitor); + + byte[] remoteBytes = getByteStore().getBytes(resource); + if (remoteBytes == null) { + // There is no remote handle for this resource + return null; + } + + if (resource.getType() == IResource.FILE) { + if (immutable) { + remoteBytes = ResourceSyncInfo.setTag(remoteBytes, new CVSTag(ResourceSyncInfo.getRevision(remoteBytes), CVSTag.VERSION)); + } + if (parent == null) { + return (ICVSRemoteResource)getResourceVariant(resource); + } + return new RemoteFile(parent, remoteBytes); + } else { + RemoteFolderTree remote = RemoteFolderTree.fromBytes(parent, resource, remoteBytes); + IResource[] members = members(resource); + List children = new ArrayList(); + for (int i = 0; i < members.length; i++) { + IResource member = members[i]; + ICVSRemoteResource child = buildTree(remote, member, immutable, monitor); + if (child != null) + children.add(child); + } + + // Add the children to the remote folder tree + remote.setChildren((ICVSRemoteResource[])children.toArray(new ICVSRemoteResource[children.size()])); + return remote; + } + } } diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/CVSMergeContext.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/CVSMergeContext.java new file mode 100644 index 000000000..b5cec8d68 --- /dev/null +++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/CVSMergeContext.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ccvs.ui.actions; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceTraversal; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; +import org.eclipse.team.core.TeamException; +import org.eclipse.team.core.synchronize.SyncInfo; +import org.eclipse.team.internal.ccvs.core.CVSSyncInfo; +import org.eclipse.team.internal.ccvs.ui.subscriber.WorkspaceSynchronizeParticipant; +import org.eclipse.team.internal.ui.mapping.ResourceMappingScope; +import org.eclipse.team.ui.mapping.*; +import org.eclipse.team.ui.synchronize.ISynchronizeScope; + +public class CVSMergeContext extends MergeContext { + + private WorkspaceSynchronizeParticipant participant; + + public static IMergeContext createContext(IResourceMappingOperationInput input, IProgressMonitor monitor) { + WorkspaceSynchronizeParticipant participant = new WorkspaceSynchronizeParticipant(input.asSynchronizationScope()); + participant.refreshNow(participant.getResources(), NLS.bind("Preparing to merge {0}", new String[] { "TODO: mapping description for CVS merge context initialization" }), monitor); + return new CVSMergeContext(THREE_WAY, participant, input); + } + + protected CVSMergeContext(String type, WorkspaceSynchronizeParticipant participant, IResourceMappingOperationInput input) { + super(type, participant.getSyncInfoSet(), input); + this.participant = participant; + } + + public IStatus markAsMerged(IFile file, IProgressMonitor monitor) { + try { + SyncInfo info = getSyncInfoTree().getSyncInfo(file); + if (info instanceof CVSSyncInfo) { + CVSSyncInfo cvsInfo = (CVSSyncInfo) info; + cvsInfo.makeOutgoing(monitor); + } + return Status.OK_STATUS; + } catch (TeamException e) { + return e.getStatus(); + } + } + + public void dispose() { + participant.dispose(); + super.dispose(); + } + + public SyncInfo getSyncInfo(IResource resource) throws CoreException { + return participant.getSubscriber().getSyncInfo(resource); + } + + public void refresh(ResourceTraversal[] traversals, int flags, IProgressMonitor monitor) throws CoreException { + IResource[] resources = new ResourceMappingScope("", getResourceMappings(ALL_MAPPINGS), traversals).getRoots(); + participant.refreshNow(resources, "TODO: CVS Merge Context Refresh", monitor); + } + + public ISynchronizeScope getScope() { + return (ResourceMappingScope)participant.getScope(); + } + +} diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/UpdateModelAction.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/UpdateModelAction.java new file mode 100644 index 000000000..f990a6cbd --- /dev/null +++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/UpdateModelAction.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ccvs.ui.actions; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.action.IAction; +import org.eclipse.team.internal.ccvs.core.CVSException; +import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin; +import org.eclipse.team.internal.ccvs.ui.Policy; +import org.eclipse.team.internal.ccvs.ui.operations.CacheBaseContentsOperation; +import org.eclipse.team.internal.ccvs.ui.operations.CacheRemoteContentsOperation; +import org.eclipse.team.ui.mapping.*; +import org.eclipse.ui.IWorkbenchPart; + +/** + * Action that runs an update without prompting the user for a tag. + * + * @since 3.1 + */ +public class UpdateModelAction extends WorkspaceTraversalAction { + + private class CVSMergeOperation extends ResourceMappingMergeOperation { + + protected CVSMergeOperation(IWorkbenchPart part, IResourceMappingOperationInput input) { + super(part, input); + } + + protected IMergeContext buildMergeContext(IProgressMonitor monitor) { + monitor.beginTask(null, 100); + IMergeContext context = CVSMergeContext.createContext(getInput(), Policy.subMonitorFor(monitor, 50)); + // cache the base and remote contents + // TODO: Refreshing and caching now takes 3 round trips. + // OPTIMIZE: remote state and contents could be obtained in 1 + // OPTIMIZE: Based could be avoided if we always cached base locally + try { + new CacheBaseContentsOperation(getPart(), getInput().getInputMappings(), context.getSyncInfoTree(), true).run(Policy.subMonitorFor(monitor, 25)); + new CacheRemoteContentsOperation(getPart(), getInput().getInputMappings(), context.getSyncInfoTree()).run(Policy.subMonitorFor(monitor, 25)); + } catch (InvocationTargetException e) { + CVSUIPlugin.log(CVSException.wrapException(e)); + } catch (InterruptedException e) { + // Ignore + } + monitor.done(); + return context; + } + + protected void requiresManualMerge(ModelProvider[] providers, IMergeContext context) throws CoreException { + // TODO Auto-generated method stub + } + + } + + /* + * @see org.eclipse.team.internal.ccvs.ui.actions.WorkspaceAction#isEnabledForAddedResources() + */ + protected boolean isEnabledForAddedResources() { + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.team.internal.ccvs.ui.actions.WorkspaceAction#isEnabledForNonExistantResources() + */ + protected boolean isEnabledForNonExistantResources() { + return true; + } + + public void execute(IAction action) throws InterruptedException, InvocationTargetException { + new CVSMergeOperation(getTargetPart(), getOperationInput()).run(); + } + + public String getId() { + return "org.eclipse.team.cvs.ui.modelupdate"; + } +} diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/WorkspaceTraversalAction.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/WorkspaceTraversalAction.java index afe30be50..96a022919 100644 --- a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/WorkspaceTraversalAction.java +++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/WorkspaceTraversalAction.java @@ -15,13 +15,17 @@ import java.util.*; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.mapping.*; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.*; +import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.team.core.RepositoryProvider; import org.eclipse.team.core.subscribers.Subscriber; import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin; +import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin; import org.eclipse.team.internal.core.subscribers.SubscriberResourceMappingContext; +import org.eclipse.team.internal.ui.dialogs.AdditionalMappingsDialog; +import org.eclipse.team.internal.ui.mapping.SimpleResourceMappingOperationInput; +import org.eclipse.team.ui.mapping.*; import org.eclipse.ui.PlatformUI; @@ -38,9 +42,37 @@ public abstract class WorkspaceTraversalAction extends WorkspaceAction { * within a CVS managed project */ protected ResourceMapping[] getCVSResourceMappings() { - return getSelectedResourceMappings(CVSProviderPlugin.getTypeId()); + ResourceMapping[] selectedMappings = getSelectedResourceMappings(CVSProviderPlugin.getTypeId()); + try { + IResourceMappingOperationInput input = new ResourceMappingOperationInput(selectedMappings, ResourceMappingContext.LOCAL_CONTEXT); + input.buildInput(new NullProgressMonitor()); + if (input.hasAdditionalMappings()) { + ResourceMapping[] allMappings = input.getInputMappings(); + return showAllMappings(selectedMappings, allMappings); + } + } catch (CoreException e) { + CVSUIPlugin.log(e); + } + return selectedMappings; } + private ResourceMapping[] showAllMappings(final ResourceMapping[] selectedMappings, final ResourceMapping[] allMappings) { + final boolean[] canceled = new boolean[] { false }; + getShell().getDisplay().syncExec(new Runnable() { + public void run() { + AdditionalMappingsDialog dialog = new AdditionalMappingsDialog(getShell(), "Participating Elements", selectedMappings, new TeamViewerContext(allMappings)); + int result = dialog.open(); + canceled[0] = result != Dialog.OK; + } + + }); + + if (canceled[0]) { + return new ResourceMapping[0]; + } + return allMappings; + } + protected static IResource[] getRootTraversalResources(ResourceMapping[] mappings, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { List result = new ArrayList(); for (int i = 0; i < mappings.length; i++) { @@ -68,7 +100,15 @@ public abstract class WorkspaceTraversalAction extends WorkspaceAction { return getResourcesToCompare(getCVSResourceMappings(), subscriber); } - public static IResource[] getResourcesToCompare(final ResourceMapping[] mappings, final Subscriber subscriber) throws InvocationTargetException { + protected ResourceMappingContext getResourceMappingContext() { + return SubscriberResourceMappingContext.getCompareContext(CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber()); + } + + protected SimpleResourceMappingOperationInput getOperationInput() { + return new SimpleResourceMappingOperationInput(getSelectedResourceMappings(CVSProviderPlugin.getTypeId()), getResourceMappingContext()); + } + + public static IResource[] getResourcesToCompare(final ResourceMapping[] mappings, final Subscriber subscriber) throws InvocationTargetException { // Determine what resources need to be synchronized. // Use a resource mapping context to include any relevant remote resources final IResource[][] resources = new IResource[][] { null }; diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CacheBaseContentsOperation.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CacheBaseContentsOperation.java new file mode 100644 index 000000000..4b0e08ddc --- /dev/null +++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CacheBaseContentsOperation.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ccvs.ui.operations; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.team.core.TeamException; +import org.eclipse.team.core.synchronize.SyncInfo; +import org.eclipse.team.core.synchronize.SyncInfoTree; +import org.eclipse.team.core.variants.IResourceVariant; +import org.eclipse.team.internal.ccvs.core.*; +import org.eclipse.team.internal.ccvs.core.resources.RemoteFile; +import org.eclipse.ui.IWorkbenchPart; + +/** + * Operation that ensures that the contents for base + * of each local resource is cached. + */ +public class CacheBaseContentsOperation extends CacheTreeContentsOperation { + + private final boolean includeOutgoing; + + public CacheBaseContentsOperation(IWorkbenchPart part, ResourceMapping[] mappers, SyncInfoTree tree, boolean includeOutgoing) { + super(part, mappers, tree); + this.includeOutgoing = includeOutgoing; + } + + protected boolean needsContents(SyncInfo info) { + IResource local = info.getLocal(); + IResourceVariant base = info.getBase(); + if (base != null && local.getType() == IResource.FILE) { + int direction = SyncInfo.getDirection(info.getKind()); + if (isEnabledForDirection(direction)) { + if (base instanceof RemoteFile) { + RemoteFile remote = (RemoteFile) base; + if (!remote.isContentsCached()) { + return true; + } + } + } + } + return false; + } + + private boolean isEnabledForDirection(int direction) { + return direction == SyncInfo.CONFLICTING || + (includeOutgoing && direction == SyncInfo.OUTGOING); + } + + protected ICVSRemoteResource buildTree(CVSTeamProvider provider) throws TeamException { + return CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber().buildBaseTree(provider.getProject(), true, new NullProgressMonitor()); + } + +} diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CacheRemoteContentsOperation.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CacheRemoteContentsOperation.java new file mode 100644 index 000000000..73a0d0e8c --- /dev/null +++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CacheRemoteContentsOperation.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ccvs.ui.operations; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.team.core.TeamException; +import org.eclipse.team.core.synchronize.SyncInfo; +import org.eclipse.team.core.synchronize.SyncInfoTree; +import org.eclipse.team.core.variants.IResourceVariant; +import org.eclipse.team.internal.ccvs.core.*; +import org.eclipse.team.internal.ccvs.core.resources.RemoteFile; +import org.eclipse.ui.IWorkbenchPart; + +/** + * Operation that ensures that the contents for remote + * of each local resource is cached. + */ +public class CacheRemoteContentsOperation extends CacheTreeContentsOperation { + + public CacheRemoteContentsOperation(IWorkbenchPart part, ResourceMapping[] mappers, SyncInfoTree tree) { + super(part, mappers, tree); + } + + protected boolean needsContents(SyncInfo info) { + IResource local = info.getLocal(); + IResourceVariant remote = info.getRemote(); + if (remote != null && local.getType() == IResource.FILE) { + int direction = SyncInfo.getDirection(info.getKind()); + if (direction == SyncInfo.CONFLICTING || + direction == SyncInfo.INCOMING) { + if (remote instanceof RemoteFile) { + RemoteFile remoteFile = (RemoteFile) remote; + if (!remoteFile.isContentsCached()) { + return true; + } + } + } + } + return false; + } + + protected ICVSRemoteResource buildTree(CVSTeamProvider provider) throws TeamException { + return CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber().buildRemoteTree(provider.getProject(), true, new NullProgressMonitor()); + } + +} diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CacheTreeContentsOperation.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CacheTreeContentsOperation.java new file mode 100644 index 000000000..89d0c98ff --- /dev/null +++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CacheTreeContentsOperation.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ccvs.ui.operations; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +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.SyncInfoTree; +import org.eclipse.team.internal.ccvs.core.*; +import org.eclipse.team.internal.ccvs.core.client.*; +import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption; +import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin; +import org.eclipse.ui.IWorkbenchPart; + +/** + * Abstract operation for caching the contents for any files + * in a particular remote tree that differ from the local contents.* + */ +public abstract class CacheTreeContentsOperation extends SingleCommandOperation { + + private final SyncInfoTree tree; + + public CacheTreeContentsOperation(IWorkbenchPart part, ResourceMapping[] mappers, SyncInfoTree tree) { + super(part, mappers, Command.NO_LOCAL_OPTIONS); + this.tree = tree; + } + + protected void execute(CVSTeamProvider provider, IResource[] resources, boolean recurse, IProgressMonitor monitor) throws CVSException, InterruptedException { + IResource[] files = getFilesWithUncachedContents(resources, recurse); + if (files.length > 0) + super.execute(provider, files, recurse, monitor); + } + + private IResource[] getFilesWithUncachedContents(IResource[] resources, boolean recurse) { + ArrayList result = new ArrayList(); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + SyncInfo[] infos = tree.getSyncInfos(resource, recurse ? IResource.DEPTH_INFINITE: IResource.DEPTH_ONE); + for (int j = 0; j < infos.length; j++) { + SyncInfo info = infos[j]; + if (needsContents(info)) { + result.add(info.getLocal()); + } + } + } + return (IResource[]) result.toArray(new IResource[result.size()]); + } + + protected abstract boolean needsContents(SyncInfo info); + + /* (non-Javadoc) + * + * Use a local root that is really the base tree so we can cache + * the base contents without affecting the local contents. + * + * @see org.eclipse.team.internal.ccvs.ui.operations.RepositoryProviderOperation#getLocalRoot(org.eclipse.team.internal.ccvs.core.CVSTeamProvider) + */ + protected ICVSFolder getLocalRoot(CVSTeamProvider provider) + throws CVSException { + try { + ICVSRemoteResource tree = buildTree(provider); + return (ICVSFolder)tree; + } catch (TeamException e) { + throw CVSException.wrapException(e); + } + } + + protected abstract ICVSRemoteResource buildTree(CVSTeamProvider provider) throws TeamException; + + /* (non-Javadoc) + * @see org.eclipse.team.internal.ccvs.ui.operations.RepositoryProviderOperation#getCVSArguments(org.eclipse.core.resources.IResource[]) + */ + protected ICVSResource[] getCVSArguments(Session session, IResource[] resources) { + List result = new ArrayList(); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + try { + ICVSResource file = session.getLocalRoot().getChild(resource.getProjectRelativePath().toString()); + result.add(file); + } catch (CVSException e) { + // Log and continue + CVSUIPlugin.log(e); + } + } + + return (ICVSResource[]) result.toArray(new ICVSResource[result.size()]); + } + + protected IStatus executeCommand(Session session, CVSTeamProvider provider, ICVSResource[] resources, boolean recurse, IProgressMonitor monitor) throws CVSException, InterruptedException { + return Command.UPDATE.execute( + session, + Command.NO_GLOBAL_OPTIONS, + getLocalOptions(true), + resources, + null, + monitor); + } + + protected LocalOption[] getLocalOptions(boolean recurse) { + return Update.IGNORE_LOCAL_CHANGES.addTo(super.getLocalOptions(recurse)); + } + + protected String getTaskName(CVSTeamProvider provider) { + return NLS.bind("Fetching contents for changed files in {0}", new String[] {provider.getProject().getName()}); + } + + protected String getTaskName() { + return "Fetching contents for changed files"; + } + +} diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/SingleCommandOperation.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/SingleCommandOperation.java index acb139e3a..731ddee0c 100644 --- a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/SingleCommandOperation.java +++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/SingleCommandOperation.java @@ -39,7 +39,7 @@ public abstract class SingleCommandOperation extends RepositoryProviderOperation Session session = new Session(getRemoteLocation(provider), getLocalRoot(provider), true /* output to console */); session.open(Policy.subMonitorFor(monitor, 10), isServerModificationOperation()); try { - IStatus status = executeCommand(session, provider, getCVSArguments(resources), recurse, Policy.subMonitorFor(monitor, 90)); + IStatus status = executeCommand(session, provider, getCVSArguments(session, resources), recurse, Policy.subMonitorFor(monitor, 90)); if (isReportableError(status)) { throw new CVSException(status); } @@ -48,7 +48,15 @@ public abstract class SingleCommandOperation extends RepositoryProviderOperation } } - /* (non-Javadoc) + protected final ICVSResource[] getCVSArguments(IResource[] resources) { + return super.getCVSArguments(resources); + } + + protected ICVSResource[] getCVSArguments(Session session, IResource[] resources) { + return getCVSArguments(resources); + } + + /* (non-Javadoc) * @see org.eclipse.team.internal.ccvs.ui.operations.RepositoryProviderOperation#execute(org.eclipse.team.internal.ccvs.core.CVSTeamProvider, org.eclipse.team.internal.ccvs.ui.operations.RepositoryProviderOperation.ICVSTraversal, org.eclipse.core.runtime.IProgressMonitor) */ protected void execute(CVSTeamProvider provider, ICVSTraversal entry, IProgressMonitor monitor) throws CVSException, InterruptedException { diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/WorkspaceResourceMapper.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/WorkspaceResourceMapper.java index 44df6751e..f7593785e 100644 --- a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/WorkspaceResourceMapper.java +++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/WorkspaceResourceMapper.java @@ -24,6 +24,9 @@ import org.eclipse.core.runtime.IProgressMonitor; * The resulting mapper will return the workspace root as the model * object. * + * TODO: The ability to wrap multiple resources in a single mapping + * should be provided by the resources plugin. + * * @since 3.1 */ public final class WorkspaceResourceMapper extends ResourceMapping { diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/RefreshDirtyStateOperation.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/RefreshDirtyStateOperation.java index f04b7367f..e47739bac 100644 --- a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/RefreshDirtyStateOperation.java +++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/RefreshDirtyStateOperation.java @@ -10,29 +10,21 @@ *******************************************************************************/ package org.eclipse.team.internal.ccvs.ui.subscriber; -import java.util.ArrayList; -import java.util.List; +import java.lang.reflect.InvocationTargetException; import org.eclipse.compare.structuremergeviewer.IDiffElement; import org.eclipse.core.resources.*; +import org.eclipse.core.resources.mapping.ResourceMapping; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.team.core.TeamException; import org.eclipse.team.core.synchronize.*; import org.eclipse.team.core.synchronize.SyncInfoFilter.ContentComparisonSyncInfoFilter; -import org.eclipse.team.core.variants.IResourceVariant; import org.eclipse.team.internal.ccvs.core.*; -import org.eclipse.team.internal.ccvs.core.client.*; -import org.eclipse.team.internal.ccvs.core.client.Command; -import org.eclipse.team.internal.ccvs.core.client.Session; -import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption; -import org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation; -import org.eclipse.team.internal.ccvs.core.connection.CVSServerException; -import org.eclipse.team.internal.ccvs.core.resources.*; -import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo; -import org.eclipse.team.internal.ccvs.core.util.KnownRepositories; +import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot; import org.eclipse.team.internal.ccvs.ui.CVSUIMessages; import org.eclipse.team.internal.ccvs.ui.Policy; +import org.eclipse.team.internal.ccvs.ui.operations.CacheBaseContentsOperation; import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration; /** @@ -54,7 +46,7 @@ public class RefreshDirtyStateOperation extends CVSSubscriberOperation { monitor.beginTask(null, 200); IProject project = infos[0].getLocal().getProject(); ICVSFolder folder = CVSWorkspaceRoot.getCVSFolderFor(project); - ensureBaseContentsCached(folder, infos, Policy.subMonitorFor(monitor, 100)); + ensureBaseContentsCached(project, infos, Policy.subMonitorFor(monitor, 100)); folder.run(new ICVSRunnable() { public void run(IProgressMonitor monitor) throws CVSException { monitor.beginTask(null, infos.length * 100); @@ -74,71 +66,15 @@ public class RefreshDirtyStateOperation extends CVSSubscriberOperation { monitor.done(); } - private void ensureBaseContentsCached(ICVSFolder project, SyncInfo[] infos, IProgressMonitor monitor) throws CVSException { - ICVSRepositoryLocation location = getRemoteLocation(project); - if (location == null) return; - monitor.beginTask(null, 100); - SyncInfo[] needContents = getBaseFilesWithUncachedContents(infos, Policy.subMonitorFor(monitor, 10)); - if (needContents.length == 0) return; - RemoteFolderTree tree = RemoteFolderTreeBuilder.buildBaseTree((CVSRepositoryLocation)location , project, null, Policy.subMonitorFor(monitor, 20)); - ICVSFile[] files = getFilesToUpdate(tree, infos); - replaceContents(location, tree, files, Policy.subMonitorFor(monitor, 70)); - monitor.done(); - } - - private ICVSFile[] getFilesToUpdate(RemoteFolderTree tree, SyncInfo[] infos) throws CVSException { - List newFiles = new ArrayList(); - for (int i = 0; i < infos.length; i++) { - SyncInfo info = infos[i]; - ICVSFile file = tree.getFile(info.getLocal().getProjectRelativePath().toString()); - newFiles.add(file); - } - - return (ICVSFile[]) newFiles.toArray(new ICVSFile[newFiles.size()]); - } - - private void replaceContents(ICVSRepositoryLocation location, ICVSFolder project, ICVSFile[] files, IProgressMonitor monitor) throws CVSException { - monitor.beginTask(null, 100); - Session session = new Session(location, project, false); - try { - session.open(Policy.subMonitorFor(monitor, 10)); - IStatus execute = Command.UPDATE.execute( - session, - Command.NO_GLOBAL_OPTIONS, - new LocalOption[] { Update.IGNORE_LOCAL_CHANGES }, - files, - null, - Policy.subMonitorFor(monitor, 90)); - if (execute.getCode() == CVSStatus.SERVER_ERROR) { - throw new CVSServerException(execute); - } - } finally { - session.close(); - } - - } - - private SyncInfo[] getBaseFilesWithUncachedContents(SyncInfo[] infos, IProgressMonitor monitor) { - List files = new ArrayList(); - for (int i = 0; i < infos.length; i++) { - SyncInfo info = infos[i]; - IResourceVariant base = info.getBase(); - if (base instanceof RemoteFile) { - RemoteFile remote = (RemoteFile) base; - if (!remote.isContentsCached()) { - files.add(info); - } - } - } - return (SyncInfo[]) files.toArray(new SyncInfo[files.size()]); - } - - private ICVSRepositoryLocation getRemoteLocation(ICVSFolder project) throws CVSException { - FolderSyncInfo info = project.getFolderSyncInfo(); - if (info == null) { - return null; - } - return KnownRepositories.getInstance().getRepository(info.getRoot()); + private void ensureBaseContentsCached(IProject project, SyncInfo[] infos, IProgressMonitor monitor) throws CVSException { + try { + new CacheBaseContentsOperation(getPart(), new ResourceMapping[] { (ResourceMapping)project.getAdapter(ResourceMapping.class) }, + new SyncInfoTree(infos), true).run(monitor); + } catch (InvocationTargetException e) { + throw CVSException.wrapException(e); + } catch (InterruptedException e) { + throw new OperationCanceledException(); + } } protected String getErrorTitle() { diff --git a/bundles/org.eclipse.team.ui/META-INF/MANIFEST.MF b/bundles/org.eclipse.team.ui/META-INF/MANIFEST.MF index 78d8969cf..55fb05126 100644 --- a/bundles/org.eclipse.team.ui/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.team.ui/META-INF/MANIFEST.MF @@ -9,12 +9,14 @@ Bundle-Localization: plugin Export-Package: org.eclipse.team.internal.ui;x-friends:="org.eclipse.team.cvs.ssh,org.eclipse.team.cvs.ssh2,org.eclipse.team.cvs.ui", org.eclipse.team.internal.ui.actions;x-friends:="org.eclipse.team.cvs.ssh,org.eclipse.team.cvs.ssh2,org.eclipse.team.cvs.ui", org.eclipse.team.internal.ui.dialogs;x-friends:="org.eclipse.team.cvs.ssh,org.eclipse.team.cvs.ssh2,org.eclipse.team.cvs.ui", + org.eclipse.team.internal.ui.mapping, org.eclipse.team.internal.ui.preferences;x-friends:="org.eclipse.team.cvs.ssh,org.eclipse.team.cvs.ssh2,org.eclipse.team.cvs.ui", org.eclipse.team.internal.ui.registry;x-friends:="org.eclipse.team.cvs.ssh,org.eclipse.team.cvs.ssh2,org.eclipse.team.cvs.ui", org.eclipse.team.internal.ui.synchronize;x-friends:="org.eclipse.team.cvs.ssh,org.eclipse.team.cvs.ssh2,org.eclipse.team.cvs.ui", org.eclipse.team.internal.ui.synchronize.actions;x-friends:="org.eclipse.team.cvs.ssh,org.eclipse.team.cvs.ssh2,org.eclipse.team.cvs.ui", org.eclipse.team.internal.ui.wizards;x-friends:="org.eclipse.team.cvs.ssh,org.eclipse.team.cvs.ssh2,org.eclipse.team.cvs.ui", org.eclipse.team.ui, + org.eclipse.team.ui.mapping, org.eclipse.team.ui.synchronize Require-Bundle: org.eclipse.ui.ide;resolution:=optional, org.eclipse.core.resources, diff --git a/bundles/org.eclipse.team.ui/plugin.xml b/bundles/org.eclipse.team.ui/plugin.xml index 59c9f6132..bdeb1c8b9 100644 --- a/bundles/org.eclipse.team.ui/plugin.xml +++ b/bundles/org.eclipse.team.ui/plugin.xml @@ -314,6 +314,11 @@ class="org.eclipse.team.internal.ui.TeamAdapterFactory"> <adapter type="org.eclipse.ui.model.IWorkbenchAdapter"/> </factory> + <factory + adaptableType="org.eclipse.core.resources.mapping.ModelProvider" + class="org.eclipse.team.internal.ui.TeamAdapterFactory"> + <adapter type="org.eclipse.team.internal.ui.mapping.INavigatorContentExtensionFactory"/> + </factory> </extension> </plugin> diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/ResourceMappingContentProvider.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/ResourceMappingContentProvider.java new file mode 100644 index 000000000..1d3deb31c --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/ResourceMappingContentProvider.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui; + +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.team.internal.ui.mapping.IResourceMappingContentProvider; +import org.eclipse.team.ui.mapping.ITeamViewerContext; +import org.eclipse.ui.model.IWorkbenchAdapter; + +public class ResourceMappingContentProvider implements IResourceMappingContentProvider { + + /** + * TODO Should root be a resource mapping? + */ + final class RootObject implements IWorkbenchAdapter, IAdaptable { + final ResourceMapping[] mappings; + + private RootObject(ResourceMapping[] mappings) { + super(); + this.mappings = mappings; + } + + public Object[] getChildren(Object o) { + return mappings; + } + + public ImageDescriptor getImageDescriptor(Object object) { + return null; + } + + public String getLabel(Object o) { + return "Other Elements"; + } + + public Object getParent(Object o) { + return null; + } + + public Object getAdapter(Class adapter) { + if (adapter == IWorkbenchAdapter.class) + return this; + return null; + } + } + + final RootObject root; + private final ITeamViewerContext context; + private final String modelId; + + public ResourceMappingContentProvider(ITeamViewerContext context, String modelId) { + this.context = context; + this.modelId = modelId; + root = new RootObject(context.getResourceMappings(modelId)); + } + + public Object getRoot() { + return root; + } + + public Object[] getChildren(Object parentElement) { + if (parentElement == root) + return root.getChildren(parentElement); + return new Object[0]; + } + + public Object getParent(Object element) { + if (element == root) + return null; + ResourceMapping[] mappings = (ResourceMapping[])root.getChildren(root); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + if (element == mapping) + return root; + } + return null; + } + + public boolean hasChildren(Object element) { + if (element == root) + return true; + return false; + } + + public Object[] getElements(Object inputElement) { + return getChildren(inputElement); + } + + public void dispose() { + // Nothing to do + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // Nothing to do + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/ResourceNavigatorContentExtensionFactory.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/ResourceNavigatorContentExtensionFactory.java new file mode 100644 index 000000000..cbccb34cc --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/ResourceNavigatorContentExtensionFactory.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui; + +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.team.internal.ui.dialogs.ResourceMappingLabelProvider; +import org.eclipse.team.internal.ui.mapping.*; +import org.eclipse.team.ui.mapping.*; + +public class ResourceNavigatorContentExtensionFactory implements + INavigatorContentExtensionFactory { + + public NavigatorContentExtension createProvider(ITeamViewerContext context) { + return new NavigatorContentExtension(context) { + private ResourceMappingContentProvider resourceMappingContentProvider; + public IResourceMappingContentProvider getContentProvider() { + if (resourceMappingContentProvider == null) + resourceMappingContentProvider = new ResourceMappingContentProvider(getContext(), ModelProvider.RESOURCE_MODEL_PROVIDER_ID); + return resourceMappingContentProvider; + } + public void dispose() { + resourceMappingContentProvider.dispose(); + super.dispose(); + } + }; + } + + public ILabelProvider getLabelProvider() { + return new ResourceMappingLabelProvider(); + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/TeamAdapterFactory.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/TeamAdapterFactory.java index 59fb42af7..f7b8a1976 100644 --- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/TeamAdapterFactory.java +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/TeamAdapterFactory.java @@ -11,7 +11,9 @@ package org.eclipse.team.internal.ui; import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.core.resources.mapping.ModelProvider; import org.eclipse.core.runtime.IAdapterFactory; +import org.eclipse.team.internal.ui.mapping.INavigatorContentExtensionFactory; import org.eclipse.team.internal.ui.synchronize.DiffNodeWorkbenchAdapter; import org.eclipse.ui.model.IWorkbenchAdapter; @@ -27,6 +29,9 @@ public class TeamAdapterFactory implements IAdapterFactory { if(adaptableObject instanceof DiffNode && adapterType == IWorkbenchAdapter.class) { return diffNodeAdapter; } + if (adaptableObject instanceof ModelProvider && adapterType == INavigatorContentExtensionFactory.class) { + return new ResourceNavigatorContentExtensionFactory(); + } return null; } @@ -35,6 +40,6 @@ public class TeamAdapterFactory implements IAdapterFactory { */ public Class[] getAdapterList() { // TODO Auto-generated method stub - return new Class[] {IWorkbenchAdapter.class}; + return new Class[] {IWorkbenchAdapter.class, INavigatorContentExtensionFactory.class}; } } diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/AdditionalMappingsDialog.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/AdditionalMappingsDialog.java new file mode 100644 index 000000000..a0743379b --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/AdditionalMappingsDialog.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.dialogs; + +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.team.ui.mapping.ITeamViewerContext; + +public class AdditionalMappingsDialog extends DetailsDialog { + + private final ResourceMapping[] selectedMappings; + private ResourceMappingSelectionArea selectedMappingsArea; + private ResourceMappingHierarchyArea allMappingsArea; + private ITeamViewerContext context; + + public AdditionalMappingsDialog(Shell parentShell, String dialogTitle, ResourceMapping[] selectedMappings, ITeamViewerContext context) { + super(parentShell, dialogTitle); + this.selectedMappings = selectedMappings; + this.context = context; + } + + protected void createMainDialogArea(Composite parent) { + createWrappingLabel(parent, "This dialog shows all the selected elements and all the elements that will be operated on by this operation due to the files in which the elements are stored."); + createSelectedMappingsArea(parent); + createAllMappingsArea(parent); + } + + /* + * Create a list that allows the selection of mappings via checkbox + */ + private void createSelectedMappingsArea(Composite parent) { + Composite composite = createComposite(parent); + selectedMappingsArea = new ResourceMappingSelectionArea(selectedMappings, false, false); + selectedMappingsArea.setDescription("Selected Elements"); + //selectedMappingsArea.addPropertyChangeListener(this); + selectedMappingsArea.createArea(composite); + // Create a separator between the two sets of buttons + Label seperator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL); + seperator.setLayoutData(new GridData (GridData.FILL_HORIZONTAL)); + } + + /* + * Create a list that allows the selection of mappings via checkbox + */ + private void createAllMappingsArea(Composite parent) { + Composite composite = createComposite(parent); + allMappingsArea = ResourceMappingHierarchyArea.create(context); + allMappingsArea.setDescription("All elements to be operated on"); + //allMappingsArea.addPropertyChangeListener(this); + allMappingsArea.createArea(composite); + // Create a separator between the two sets of buttons + Label seperator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL); + seperator.setLayoutData(new GridData (GridData.FILL_HORIZONTAL)); + } + + protected Composite createDropDownDialogArea(Composite parent) { + // TODO Auto-generated method stub + return null; + } + + protected void updateEnablements() { + // TODO Auto-generated method stub + + } + + protected boolean includeDetailsButton() { + return false; + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/MappingSelectionDialog.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/MappingSelectionDialog.java index 2b46d7419..ef4ad93ac 100644 --- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/MappingSelectionDialog.java +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/MappingSelectionDialog.java @@ -54,7 +54,7 @@ public abstract class MappingSelectionDialog extends DetailsDialog implements IP */ private void createMappingSelectionArea(Composite parent) { Composite composite = createComposite(parent); - mappingArea = new ResourceMappingSelectionArea(mappings); + mappingArea = new ResourceMappingSelectionArea(mappings, true, true); mappingArea.setDescription(getMultipleMappingsMessage()); mappingArea.addPropertyChangeListener(this); mappingArea.createArea(composite); diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingHierarchyArea.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingHierarchyArea.java new file mode 100644 index 000000000..be39a4ccc --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingHierarchyArea.java @@ -0,0 +1,278 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.dialogs; + +import java.util.*; + +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.viewers.*; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.team.internal.ui.TeamUIPlugin; +import org.eclipse.team.internal.ui.mapping.*; +import org.eclipse.team.ui.mapping.*; + +public class ResourceMappingHierarchyArea extends DialogArea { + + private String description; + private TreeViewer viewer; + private final CompositeContentProvider contentProvider; + + /* + * TODO: There are some potential problems here + * - the input changed method probably should not be propagated to the + * sub-providers. Perhaps an additional method is needed (setViewer)? + * - this content provider has state that is dependent on what is + * displayed in the view. Should a refresh of the viewer clear this state? + * I don't think it needs to unless the input changes (which it never does + * after the first set). + */ + private static class CompositeContentProvider implements IResourceMappingContentProvider, ILabelProvider { + + private final Map providers; // Map of ModelProvider -> NavigatorContentExtension + private final Map providerMap = new HashMap(); + private final ILabelProvider defaultLabelProvider = new ResourceMappingLabelProvider(); + + public CompositeContentProvider(Map providers) { + this.providers = providers; + } + + public Object getRoot() { + IResourceMappingContentProvider provider = getSingleProvider(); + if (provider != null) { + Object root = provider.getRoot(); + providerMap.put(root, getNavigatorContentExtension(root)); + return root; + } + return this; + } + + public Object[] getChildren(Object parentElement) { + IResourceMappingContentProvider singleProvider = getSingleProvider(); + if (singleProvider != null) { + return singleProvider.getChildren(parentElement); + } + if (parentElement == this) { + List result = new ArrayList(); + for (Iterator iter = providers.values().iterator(); iter.hasNext();) { + NavigatorContentExtension extension = (NavigatorContentExtension) iter.next(); + IResourceMappingContentProvider provider = extension.getContentProvider(); + Object element = provider.getRoot(); + providerMap.put(element, extension); + result.add(element); + } + return result.toArray(new Object[result.size()]); + } else { + NavigatorContentExtension extension = getNavigatorContentExtension(parentElement); + if (extension != null) { + Object[] elements = extension.getContentProvider().getChildren(parentElement); + for (int i = 0; i < elements.length; i++) { + Object element = elements[i]; + providerMap.put(element, extension); + } + return elements; + } + } + return new Object[0]; + } + + public Object getParent(Object element) { + IResourceMappingContentProvider singleProvider = getSingleProvider(); + if (singleProvider != null) { + return singleProvider.getParent(element); + } + if (element == this) + return null; + IResourceMappingContentProvider provider = getProvider(element); + if (element == provider.getRoot()) { + return this; + } + return provider.getParent(element); + } + + private NavigatorContentExtension getNavigatorContentExtension(Object element) { + if (providers.size() == 1) { + return ((NavigatorContentExtension)providers.values().iterator().next()); + } + return (NavigatorContentExtension)providerMap.get(element); + } + + private IResourceMappingContentProvider getSingleProvider() { + if (providers.size() == 1) + return getNavigatorContentExtension(this).getContentProvider(); + return null; + } + + private IResourceMappingContentProvider getProvider(Object element) { + NavigatorContentExtension e = getNavigatorContentExtension(element); + if (e == null) + return null; + return e.getContentProvider(); + } + + public boolean hasChildren(Object element) { + IResourceMappingContentProvider singleProvider = getSingleProvider(); + if (singleProvider != null) { + return singleProvider.hasChildren(element); + } + if (element != this) { + IResourceMappingContentProvider provider = getProvider(element); + if (provider != null) + return provider.hasChildren(element); + } + return getChildren(element).length > 0; + } + + public Object[] getElements(Object inputElement) { + IResourceMappingContentProvider singleProvider = getSingleProvider(); + if (singleProvider != null) { + return singleProvider.getElements(inputElement); + } + if (inputElement != this) { + IResourceMappingContentProvider provider = getProvider(inputElement); + if (provider != null) + return provider.getElements(inputElement); + } + return getChildren(inputElement); + } + + public void dispose() { + providerMap.clear(); + for (Iterator iter = providers.values().iterator(); iter.hasNext();) { + NavigatorContentExtension extension = (NavigatorContentExtension) iter.next(); + extension.dispose(); + } + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + providerMap.clear(); + for (Iterator iter = providers.values().iterator(); iter.hasNext();) { + NavigatorContentExtension extension = (NavigatorContentExtension) iter.next(); + IResourceMappingContentProvider provider = extension.getContentProvider(); + provider.inputChanged(viewer, oldInput, newInput); + } + } + + private ILabelProvider getLabelProvider(Object o) { + if (o != this) { + NavigatorContentExtension e = getNavigatorContentExtension(o); + if (e != null) + return e.getLabelProvider(); + Object parent = getParent(o); + if (parent != null) + return getLabelProvider(parent); + } + return defaultLabelProvider; + } + + public Image getImage(Object element) { + return getLabelProvider(element).getImage(element); + } + + public String getText(Object element) { + return getLabelProvider(element).getText(element); + } + + public void addListener(ILabelProviderListener listener) { + defaultLabelProvider.addListener(listener); + for (Iterator iter = providers.values().iterator(); iter.hasNext();) { + NavigatorContentExtension extension = (NavigatorContentExtension) iter.next(); + ILabelProvider lp = extension.getLabelProvider(); + lp.addListener(listener); + } + } + + public boolean isLabelProperty(Object element, String property) { + return getLabelProvider(element).isLabelProperty(element, property); + } + + public void removeListener(ILabelProviderListener listener) { + defaultLabelProvider.removeListener(listener); + for (Iterator iter = providers.values().iterator(); iter.hasNext();) { + NavigatorContentExtension extension = (NavigatorContentExtension) iter.next(); + ILabelProvider lp = extension.getLabelProvider(); + lp.removeListener(listener); + } + } + + } + + public static ResourceMappingHierarchyArea create(ITeamViewerContext context) { + ModelProvider[] providers = context.getModelProviders(); + Map extensions = new HashMap(); + for (int i = 0; i < providers.length; i++) { + ModelProvider provider = providers[i]; + INavigatorContentExtensionFactory factory = getFactory(provider); + if (factory == null) { + try { + ModelProvider resourceModelProvider = ModelProvider.getModelProviderDescriptor(ModelProvider.RESOURCE_MODEL_PROVIDER_ID).getModelProvider(); + if (!extensions.containsKey(resourceModelProvider)) { + factory = getFactory(resourceModelProvider); + } + } catch (CoreException e) { + TeamUIPlugin.log(e); + } + } + if (factory != null) { + NavigatorContentExtension extension = factory.createProvider(context); + extensions.put(provider, extension); + } + } + CompositeContentProvider provider = new CompositeContentProvider(extensions); + return new ResourceMappingHierarchyArea(provider); + } + + private static INavigatorContentExtensionFactory getFactory(ModelProvider provider) { + return (INavigatorContentExtensionFactory) provider.getAdapter(INavigatorContentExtensionFactory.class); + } + + private ResourceMappingHierarchyArea(CompositeContentProvider contentProvider) { + this.contentProvider = contentProvider; + } + + public void createArea(Composite parent) { + Composite composite = createComposite(parent, 1, true); + GridLayout layout = new GridLayout(1, false); + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.verticalSpacing = 0; + layout.horizontalSpacing = 0; + composite.setLayout(layout); + + if (description != null) + createWrappingLabel(composite, description, 1); + + viewer = new TreeViewer(composite); + GridData data = new GridData(GridData.FILL_BOTH); + data.heightHint = 100; + data.widthHint = 300; + viewer.getControl().setLayoutData(data); + viewer.setContentProvider(getContentProvider()); + viewer.setLabelProvider(getContentProvider()); + viewer.setInput(getInput()); + } + + private Object getInput() { + return getContentProvider().getRoot(); + } + + private CompositeContentProvider getContentProvider() { + return contentProvider; + } + + public void setDescription(String string) { + description = string; + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingLabelProvider.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingLabelProvider.java new file mode 100644 index 000000000..574020c4b --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingLabelProvider.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.dialogs; + +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.model.WorkbenchLabelProvider; + +public class ResourceMappingLabelProvider extends LabelProvider { + WorkbenchLabelProvider provider = new WorkbenchLabelProvider(); + public String getText(Object element) { + String text = provider.getText(element); + if (text != null && text.length() > 0) + return text; + if (element instanceof ResourceMapping) { + ResourceMapping mapping = (ResourceMapping) element; + text = provider.getText(mapping.getModelObject()); + if (text != null && text.length() > 0) + return text; + } + return super.getText(element); + } + public Image getImage(Object element) { + Image image = provider.getImage(element); + if (image != null) + return image; + if (element instanceof ResourceMapping) { + ResourceMapping mapping = (ResourceMapping) element; + image = provider.getImage(mapping.getModelObject()); + if (image != null) + return image; + } + return super.getImage(element); + } + public void dispose() { + provider.dispose(); + super.dispose(); + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingResourceDisplayArea.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingResourceDisplayArea.java index 755fda92d..09e0b9d1a 100644 --- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingResourceDisplayArea.java +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingResourceDisplayArea.java @@ -11,7 +11,10 @@ package org.eclipse.team.internal.ui.dialogs; import java.lang.reflect.InvocationTargetException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.eclipse.core.resources.*; import org.eclipse.core.resources.mapping.*; @@ -19,6 +22,7 @@ import org.eclipse.core.runtime.*; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.layout.GridData; @@ -26,8 +30,11 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.team.internal.core.Policy; import org.eclipse.team.internal.ui.TeamUIPlugin; +import org.eclipse.team.internal.ui.mapping.INavigatorContentExtensionFactory; import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.model.*; +import org.eclipse.ui.model.IWorkbenchAdapter; +import org.eclipse.ui.model.WorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; import org.eclipse.ui.views.navigator.ResourceSorter; /** @@ -55,7 +62,16 @@ public class ResourceMappingResourceDisplayArea extends DialogArea { * @return it's label */ public static String getLabel(ResourceMapping mapping) { - Object o = mapping; + Object o = null; + o = mapping.getModelProvider().getAdapter(INavigatorContentExtensionFactory.class); + if (o instanceof INavigatorContentExtensionFactory) { + INavigatorContentExtensionFactory factory = (INavigatorContentExtensionFactory) o; + ILabelProvider labelProvider = factory.getLabelProvider(); + String text = labelProvider.getText(mapping); + labelProvider.dispose(); // TODO should keep label provider around + return text; + } + o = mapping; IWorkbenchAdapter workbenchAdapter = getWorkbenchAdapter((IAdaptable)o); if (workbenchAdapter == null) { Object modelObject = mapping.getModelObject(); diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingSelectionArea.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingSelectionArea.java index 72fe6cd41..64d136fc4 100644 --- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingSelectionArea.java +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/dialogs/ResourceMappingSelectionArea.java @@ -15,52 +15,21 @@ import org.eclipse.jface.viewers.*; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; import org.eclipse.team.internal.ui.TeamUIMessages; -import org.eclipse.ui.model.*; +import org.eclipse.ui.model.AdaptableList; +import org.eclipse.ui.model.BaseWorkbenchContentProvider; /** * Dialog area that displays a checkbox list of mappings. */ public class ResourceMappingSelectionArea extends DialogArea { - public class ResourceMappingLabelProvider extends LabelProvider { - WorkbenchLabelProvider provider = new WorkbenchLabelProvider(); - public String getText(Object element) { - String text = provider.getText(element); - if (text != null && text.length() > 0) - return text; - if (element instanceof ResourceMapping) { - ResourceMapping mapping = (ResourceMapping) element; - text = provider.getText(mapping.getModelObject()); - if (text != null) - return text; - } - return super.getText(element); - } - public Image getImage(Object element) { - Image image = provider.getImage(element); - if (image != null) - return image; - if (element instanceof ResourceMapping) { - ResourceMapping mapping = (ResourceMapping) element; - image = provider.getImage(mapping.getModelObject()); - if (image != null) - return image; - } - return super.getImage(element); - } - public void dispose() { - provider.dispose(); - super.dispose(); - } - } - - /** + /** * Property constant used to indicate that the selected mapping has changed. * The object associated with the property is a <code>ResourceMapping</code>. */ @@ -74,13 +43,17 @@ public class ResourceMappingSelectionArea extends DialogArea { public static final String CHECKED_MAPPINGS = "CheckedMappings"; //$NON-NLS-1$ private ResourceMapping[] mappings; - private CheckboxTableViewer viewer; + private TableViewer viewer; private ResourceMapping[] checkedMappings; private ResourceMapping selectedMapping; private String description; + private boolean supportsChecking; + private boolean supportsSelection; - public ResourceMappingSelectionArea(ResourceMapping[] mappings) { + public ResourceMappingSelectionArea(ResourceMapping[] mappings, boolean supportSelection, boolean supportChecking) { this.mappings = mappings; + this.supportsChecking = supportChecking; + this.supportsSelection = supportSelection; } /* (non-Javadoc) @@ -98,7 +71,7 @@ public class ResourceMappingSelectionArea extends DialogArea { if (description != null) createWrappingLabel(composite, description, 1); - viewer = CheckboxTableViewer.newCheckList(composite, SWT.SINGLE | SWT.BORDER); + createViewer(composite); GridData data = new GridData(GridData.FILL_BOTH); data.heightHint = 100; data.widthHint = 300; @@ -106,23 +79,31 @@ public class ResourceMappingSelectionArea extends DialogArea { viewer.setContentProvider(new BaseWorkbenchContentProvider()); viewer.setLabelProvider(new ResourceMappingLabelProvider()); viewer.setInput(new AdaptableList(mappings)); - viewer.addCheckStateListener(new ICheckStateListener() { - public void checkStateChanged(CheckStateChangedEvent event) { - ResourceMapping[] oldMappings = checkedMappings; - checkedMappings = internalGetCheckedMappings(); - if (oldMappings != checkedMappings) - firePropertyChangeChange(CHECKED_MAPPINGS, oldMappings, checkedMappings); - } - }); - viewer.addSelectionChangedListener(new ISelectionChangedListener() { - public void selectionChanged(SelectionChangedEvent event) { - ResourceMapping oldSelection = selectedMapping; - selectedMapping = internalGetSelectedMapping(); - if (oldSelection != selectedMapping) - firePropertyChangeChange(SELECTED_MAPPING, oldSelection, selectedMapping); - } + if (isSupportsSelection()) { + viewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + ResourceMapping oldSelection = selectedMapping; + selectedMapping = internalGetSelectedMapping(); + if (oldSelection != selectedMapping) + firePropertyChangeChange(SELECTED_MAPPING, oldSelection, selectedMapping); + } + }); + } + if (isSupportsChecking()) + initializeCheckboxViewer(composite); + } + + private void initializeCheckboxViewer(Composite composite) { + final CheckboxTableViewer checkboxViewer = getCheckboxTableViewer(); + checkboxViewer.addCheckStateListener(new ICheckStateListener() { + public void checkStateChanged(CheckStateChangedEvent event) { + ResourceMapping[] oldMappings = checkedMappings; + checkedMappings = internalGetCheckedMappings(); + if (oldMappings != checkedMappings) + firePropertyChangeChange(CHECKED_MAPPINGS, oldMappings, checkedMappings); + } }); - viewer.setCheckedElements(mappings); + checkboxViewer.setCheckedElements(mappings); checkedMappings = mappings; Composite buttons = createEmbeddedButtonComposite(composite); @@ -132,7 +113,7 @@ public class ResourceMappingSelectionArea extends DialogArea { selectAll.setLayoutData(new GridData(GridData.FILL_BOTH)); selectAll.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { - viewer.setAllChecked(true); + checkboxViewer.setAllChecked(true); } }); @@ -141,13 +122,27 @@ public class ResourceMappingSelectionArea extends DialogArea { deselectAll.setLayoutData(new GridData(GridData.FILL_BOTH)); deselectAll.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { - viewer.setAllChecked(false); + checkboxViewer.setAllChecked(false); } }); - } + } + + private void createViewer(Composite composite) { + if (isSupportsChecking()) + viewer = CheckboxTableViewer.newCheckList(composite, getViewerStyle()); + else + viewer = new TableViewer(new Table(composite, getViewerStyle())); + } + + private int getViewerStyle() { + int style = SWT.BORDER; + if (isSupportsSelection()) + style |= SWT.SINGLE; + return style; + } /* private */ ResourceMapping[] internalGetCheckedMappings() { - Object[] checked = viewer.getCheckedElements(); + Object[] checked = getCheckboxTableViewer().getCheckedElements(); ResourceMapping[] mappings = new ResourceMapping[checked.length]; for (int i = 0; i < checked.length; i++) { Object object = checked[i]; @@ -190,4 +185,16 @@ public class ResourceMappingSelectionArea extends DialogArea { public ResourceMapping getSelectedMapping() { return selectedMapping; } + + private CheckboxTableViewer getCheckboxTableViewer() { + return (CheckboxTableViewer)viewer; + } + + public boolean isSupportsChecking() { + return supportsChecking; + } + + public boolean isSupportsSelection() { + return supportsSelection; + } } diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/DefaultResourceMappingMerger.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/DefaultResourceMappingMerger.java new file mode 100644 index 000000000..118656d98 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/DefaultResourceMappingMerger.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.mapping; + +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.resources.mapping.ResourceTraversal; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.team.core.synchronize.SyncInfo; +import org.eclipse.team.core.synchronize.SyncInfoTree; +import org.eclipse.team.internal.ui.Policy; +import org.eclipse.team.ui.mapping.IMergeContext; +import org.eclipse.team.ui.mapping.IResourceMappingMerger; +import org.eclipse.team.ui.mapping.IResourceMappingOperationInput; +import org.eclipse.team.ui.mapping.MergeStatus; + +/** + * A default merger that delegates the merge to the merge context. + */ +public class DefaultResourceMappingMerger implements IResourceMappingMerger { + + private final ModelProvider provider; + private final IResourceMappingOperationInput input; + + public DefaultResourceMappingMerger(ModelProvider provider, IResourceMappingOperationInput input) { + this.provider = provider; + this.input = input; + } + + public IStatus merge(IMergeContext mergeContext, IProgressMonitor monitor) throws CoreException { + try { + SyncInfoTree tree = getSetToMerge(mergeContext); + monitor.beginTask(null, 100); + IStatus status = mergeContext.merge(tree, Policy.subMonitorFor(monitor, 75)); + return covertFilesToMappings(status, mergeContext); + } finally { + monitor.done(); + } + } + + private SyncInfoTree getSetToMerge(IMergeContext mergeContext) { + ResourceMapping[] mappings = input.getMappings(provider); + SyncInfoTree result = new SyncInfoTree(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + ResourceTraversal[] traversals = input.getTraversal(mapping); + SyncInfo[] infos = mergeContext.getSyncInfoTree().getSyncInfos(traversals); + for (int j = 0; j < infos.length; j++) { + SyncInfo info = infos[j]; + result.add(info); + } + } + return result; + } + + private IStatus covertFilesToMappings(IStatus status, IMergeContext mergeContext) { + if (status.getCode() == MergeStatus.CONFLICTS) { + // In general, we can't say which mapping failed so return them all + return new MergeStatus(status.getPlugin(), status.getMessage(), input.getMappings(provider)); + } + return status; + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/INavigatorContentExtensionFactory.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/INavigatorContentExtensionFactory.java new file mode 100644 index 000000000..2bdcdb029 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/INavigatorContentExtensionFactory.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.mapping; + +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.team.ui.mapping.ITeamViewerContext; + +/** + * Factory for creating a NavigatorContentProvider for + * a given team context. + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public interface INavigatorContentExtensionFactory { + + public NavigatorContentExtension createProvider(ITeamViewerContext context); + + /** + * TODO: Should not need this but I added it to make it work + */ + public ILabelProvider getLabelProvider(); +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/IResourceMappingContentProvider.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/IResourceMappingContentProvider.java new file mode 100644 index 000000000..4fa020db3 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/IResourceMappingContentProvider.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.mapping; + +import org.eclipse.jface.viewers.ITreeContentProvider; + +/** + * A model content provider is a tree content provider that is used to display + * specific elements of a logical model. The only additional attribute of a model + * content provider is that it also provides the root of the model being displayed. + * This root may be used as the viewer input (in cases where the viewer is only displaying + * a single model) or may be used as one of multiple elements appearing at the top + * level in the viewer (in cases where the viewer contains multiple models). + */ +public interface IResourceMappingContentProvider extends ITreeContentProvider { + + /** + * Returns the root element of the model tree being displayed. + * This element may or may not appear in the viewer depending on + * whether the viewer is displaying a single logical model (in which case + * the root need not be displayed) or multiple logical models (in which + * case the root needs to be displayed to separate the mappings of different + * models). + * @return the root element of the model tree being displayed. + */ + public Object getRoot(); + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/NavigatorContentExtension.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/NavigatorContentExtension.java new file mode 100644 index 000000000..062389c23 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/NavigatorContentExtension.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.mapping; + +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.team.internal.ui.dialogs.ResourceMappingLabelProvider; +import org.eclipse.team.ui.mapping.ITeamViewerContext; + +/** + * Placeholder for Common Navigator extension + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * since 3.2 + * + */ +public abstract class NavigatorContentExtension { + + private static final ResourceMappingLabelProvider RESOURCE_MAPPING_LABEL_PROVIDER = new ResourceMappingLabelProvider(); + private final ITeamViewerContext context; + + public NavigatorContentExtension(ITeamViewerContext context) { + this.context = context; + } + + /** + * Returns a content provider that can be used to display the provided + * mappings in an element hierarchy that is consistent with the model. The + * resulting hierarchy may contain additional elements as long as they are + * for organizational purposes only. Any additional elements must not + * include additional resources (i.e. <code>IResource</code>) in the + * resource mappings contained in the view. + * + * @return a content provider that will arrange the provided mappings into a + * hierarchy. + */ + public abstract IResourceMappingContentProvider getContentProvider(); + + /** + * Return a label provider that can be used to provide labels for any + * resource mappings that adapt to this factory and any model objects that + * appear in the tree created by the + * <code>IResourceMappingContentProvider</code> returned from the + * <code>createContentProvider</code>. method. + * + * @return a label provider + */ + public ILabelProvider getLabelProvider() { + return RESOURCE_MAPPING_LABEL_PROVIDER; + } + + public ITeamViewerContext getContext() { + return context; + } + + public void dispose() { + // TODO Auto-generated method stub + } +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/ResourceMappingCheckinOperation.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/ResourceMappingCheckinOperation.java new file mode 100644 index 000000000..a07cc4f2d --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/ResourceMappingCheckinOperation.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.mapping; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.team.ui.mapping.IResourceMappingOperationInput; +import org.eclipse.team.ui.mapping.ResourceMappingOperation; +import org.eclipse.ui.IWorkbenchPart; + +/** + * The check-in operation needs to delegate the check-in to the + * repository tooling somehow. The things we can do up front is to + * adjust the input using the participants. TO do this, we need the + * resource mapping context of the check-in. + * <p> + * Another thing we could potentially do is display a model diff + * tree that shows the outgoing changes. This would need to be + * integrated with the repository tooling check-in UI artifacts + * (e.g. commit comment). + * + * The steps of a check-in operation are: + * <ol> + * <li>Obtain the selection to be operated on. + * <li>Determine the projection of the selection onto resources + * using resource mappings and traversals. + * <ul> + * <li>this will require traversals from ancestor + * <li>may require traversals from remote for conflict notification + * </ul> + * <li>Ensure that all affected mappings are known + * <ul> + * <li>additional mappings may be included due to resource project + * (i.e. many-to-one case). + * <li>notify users of additional mappings that will be affected. + * <li>this list must include locally modified model elements including + * deletions. + * <li>the list could also indicate remote changes that will cause the commit + * to fail + * </ul> + * <li>Prompt the user for additional information required by check-in + * (e.g. comment) + * <ul> + * <li>This may show model elements being included in the commit + * <li>Could also show sync state and support merging + * <li>Could if support exclusion of model elements? This would be complicated. + * </ul> + * <li>Perform the check-in on the resources + * </ol> + * <p> + * Special case involving sub-file elements. + * <ul> + * <li>Level 1: prompt to indicate that the check-in will include + * additional elements (Need a way to detect this is the case). + * <li>Level 2: prompt to display the additional elements. A participant + * can provide the elements but they need to be able to include deletions. + * Display could be a flat list that makes use of Generic Navigator type API. + * <li>Level 3: Display diff tree and highlight the additional elements. + * Need to be able to identify and highlight the additional elements. + * </ul> + * One way to handle this would be to delegate the prompt to the model provider. + * That is, collect the resource mappings involved and ask the model provider + * to indicate to the user what additional resource mappings will be operated + * on and return an adjusted list. + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public abstract class ResourceMappingCheckinOperation extends ResourceMappingOperation { + + protected ResourceMappingCheckinOperation(IWorkbenchPart part, IResourceMappingOperationInput input) { + super(part, input); + } + + protected void execute(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + // TODO Auto-generated method stub + + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/ResourceMappingLoadOperation.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/ResourceMappingLoadOperation.java new file mode 100644 index 000000000..cbbe86858 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/ResourceMappingLoadOperation.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.mapping; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.team.ui.mapping.IResourceMappingOperationInput; +import org.eclipse.team.ui.mapping.ResourceMappingOperation; +import org.eclipse.ui.IWorkbenchPart; + +/** + * The steps of an load (replace with version or branch) operation are: + * <ol> + * <li>Obtain the selection to be operated on. + * <li>Determine the projection of the selection onto resources + * using resource mappings and traversals. + * <ul> + * <li>this will require traversal of remote only + * </ul> + * <li>Ensure that all affected mappings are known + * <ul> + * <li>additional mappings may be included due to resource project + * (i.e. many-to-one case). + * <li>notify users of additional mappings that will be affected + * <li>this list must include locally changed model elements whose + * changes will be lost including any that were deleted. + * <li>this list could include changed remote elements that + * will be received including additions + * </ul> + * <li>Perform the replace at the resource level + * </ol> + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 32 + */ +public class ResourceMappingLoadOperation extends ResourceMappingOperation { + + protected ResourceMappingLoadOperation(IWorkbenchPart part, IResourceMappingOperationInput input) { + super(part, input); + } + + protected void execute(IProgressMonitor monitor) + throws InvocationTargetException, InterruptedException { + // TODO Auto-generated method stub + + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/ResourceMappingScope.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/ResourceMappingScope.java new file mode 100644 index 000000000..41f4b41e3 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/ResourceMappingScope.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.mapping; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.resources.mapping.ResourceTraversal; +import org.eclipse.team.ui.synchronize.AbstractSynchronizeScope; + +/** + * A synchronize scope whose roots are defined by the traversals + * obtained from a set of resource mappings. + * @since 3.2 + */ +public class ResourceMappingScope extends AbstractSynchronizeScope { + + private ResourceMapping[] mappings; + private ResourceTraversal[] traversals; + private String name; + private IResource[] roots; + + /** + * Create a resource mapping scope. + * @param name the name used to describe the scope (this may be displayed to users) + * @param mappings the mappings that define the scope + * @param traversals the traversals derived from the mappings using the context of the operation being performed + */ + public ResourceMappingScope(String name, ResourceMapping[] mappings, ResourceTraversal[] traversals) { + this.name = name; + this.mappings = mappings; + this.traversals = traversals; + } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.synchronize.ISynchronizeScope#getName() + */ + public String getName() { + return name; + } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.synchronize.ISynchronizeScope#getRoots() + */ + public IResource[] getRoots() { + if (roots == null) { + Set result = new HashSet(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + IResource[] resources = traversal.getResources(); + for (int j = 0; j < resources.length; j++) { + IResource resource = resources[j]; + //TODO: should we check for parent/child relationships? + result.add(resource); + } + } + roots = (IResource[]) result.toArray(new IResource[result.size()]); + } + return roots; + } + + /** + * Return the resource mappings used to define this scope. + * @return the resource mappings used to define this scope + */ + public ResourceMapping[] getResourceMappings() { + return mappings; + } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.synchronize.ISynchronizeScope#contains(org.eclipse.core.resources.IResource) + */ + public boolean contains(IResource resource) { + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + if (traversal.contains(resource)) { + return true; + } + } + return false; + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/SimpleResourceMappingOperationInput.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/SimpleResourceMappingOperationInput.java new file mode 100644 index 000000000..feb958b57 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/SimpleResourceMappingOperationInput.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.mapping; + +import java.util.*; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.team.internal.ui.Policy; +import org.eclipse.team.ui.mapping.IResourceMappingOperationInput; +import org.eclipse.team.ui.synchronize.ISynchronizeScope; + +/** + * A simple implementation of an operation input that + * does not transform the input. + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public class SimpleResourceMappingOperationInput implements + IResourceMappingOperationInput { + + private ResourceMapping[] mappings; + private ResourceMappingContext context; + private Map mappingToTraversalsMap = new HashMap(); + + public SimpleResourceMappingOperationInput(ResourceMapping[] mappings, ResourceMappingContext context) { + this.mappings = mappings; + this.context = context; + } + + public ResourceMapping[] getSeedMappings() { + return mappings; + } + + public void buildInput(IProgressMonitor monitor) throws CoreException { + buildInputMappingToTraversalsMap(monitor); + } + + public ResourceMapping[] getInputMappings() { + return mappings; + } + + public ResourceTraversal[] getInputTraversals() { + Collection values = mappingToTraversalsMap.values(); + List result = new ArrayList(); + for (Iterator iter = values.iterator(); iter.hasNext();) { + ResourceTraversal[] traversals = (ResourceTraversal[]) iter.next(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + result.add(traversal); + } + } + return combineTraversals((ResourceTraversal[]) result.toArray(new ResourceTraversal[result.size()])); + } + + public ResourceTraversal[] getTraversal(ResourceMapping mapping) { + return (ResourceTraversal[])mappingToTraversalsMap.get(mapping); + } + + public boolean hasAdditionalMappings() { + return false; + } + + private void buildInputMappingToTraversalsMap(IProgressMonitor monitor) throws CoreException { + monitor.beginTask(null, mappings.length * 100); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + ResourceTraversal[] traversals = mapping.getTraversals(context, Policy.subMonitorFor(monitor, 100)); + mappingToTraversalsMap.put(mapping, traversals); + } + monitor.done(); + } + + protected static ResourceTraversal[] combineTraversals(ResourceTraversal[] allTraversals) { + Set zero = new HashSet(); + Set shallow = new HashSet(); + Set deep = new HashSet(); + for (int i = 0; i < allTraversals.length; i++) { + ResourceTraversal traversal = allTraversals[i]; + switch (traversal.getDepth()) { + case IResource.DEPTH_ZERO: + zero.addAll(Arrays.asList(traversal.getResources())); + break; + case IResource.DEPTH_ONE: + shallow.addAll(Arrays.asList(traversal.getResources())); + break; + case IResource.DEPTH_INFINITE: + deep.addAll(Arrays.asList(traversal.getResources())); + break; + } + } + List result = new ArrayList(); + if (!zero.isEmpty()) { + result.add(new ResourceTraversal((IResource[]) zero.toArray(new IResource[zero.size()]), IResource.DEPTH_ZERO, IResource.NONE)); + } + if (!shallow.isEmpty()) { + result.add(new ResourceTraversal((IResource[]) shallow.toArray(new IResource[shallow.size()]), IResource.DEPTH_ONE, IResource.NONE)); + } + if (!deep.isEmpty()) { + result.add(new ResourceTraversal((IResource[]) deep.toArray(new IResource[deep.size()]), IResource.DEPTH_INFINITE, IResource.NONE)); + } + return (ResourceTraversal[]) result.toArray(new ResourceTraversal[result.size()]); + } + + public ModelProvider[] getModelProviders() { + Set result = new HashSet(); + ResourceMapping[] mappings = getInputMappings(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + result.add(mapping.getModelProvider()); + } + return (ModelProvider[]) result.toArray(new ModelProvider[result.size()]); + } + + public ResourceMappingContext getContext() { + return context; + } + + public ResourceMapping[] getMappings(ModelProvider provider) { + Set result = new HashSet(); + ResourceMapping[] mappings = getInputMappings(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + if (mapping.getModelProviderId().equals(provider.getDescriptor().getId())) { + result.add(mapping); + } + } + return (ResourceMapping[]) result.toArray(new ResourceMapping[result.size()]); + } + + public ISynchronizeScope asSynchronizationScope() { + // TODO Temporary implementation + return new ResourceMappingScope("TODO: Need appropriate labels", getInputMappings(), getInputTraversals()); + } +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/SynchronizationOperationLabelProvider.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/SynchronizationOperationLabelProvider.java new file mode 100644 index 000000000..0aceb8f22 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/mapping/SynchronizationOperationLabelProvider.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.internal.ui.mapping; + +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.jface.viewers.*; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.graphics.*; +import org.eclipse.team.core.synchronize.SyncInfo; +import org.eclipse.team.internal.ui.*; + +public abstract class SynchronizationOperationLabelProvider extends LabelProvider { + + // Cache for folder images that have been overlayed with conflict icon + private Map fgImageCache; + + // Contains direction images + CompareConfiguration compareConfig = new CompareConfiguration(); + + /* + * (non-Javadoc) + * @see org.eclipse.jface.viewers.LabelProvider#getImage(java.lang.Object) + */ + public Image getImage(Object element) { + LabelProvider modelLabelProvider = getModelLabelProvider(); + Image base = modelLabelProvider.getImage(element); + if (base != null) { + int kind = getSyncKind(element); + Image decoratedImage; + decoratedImage = getCompareImage(base, kind); + // The reason we still overlay the compare image is to + // ensure that the image width for all images shown in the viewer + // are consistent. + return decoratedImage; + } + return base; + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object) + */ + public String getText(Object element) { + LabelProvider modelLabelProvider = getModelLabelProvider(); + String base = modelLabelProvider.getText(element); + if (isSyncInfoInTextEnabled()) { + int kind = getSyncKind(element); + if (kind != SyncInfo.IN_SYNC) { + String syncKindString = SyncInfo.kindToString(kind); + return NLS.bind(TeamUIMessages.TeamSubscriberSyncPage_labelWithSyncKind, new String[] { base, syncKindString }); // + } + } + return base; + } + + /** + * Returns whether the synchronization state should be included in the + * text of the label. By default, the Team preference is used to determine + * what to return. + * @return whether the synchronization state should be included in the + * text of the label + */ + protected boolean isSyncInfoInTextEnabled() { + return TeamUIPlugin.getPlugin().getPreferenceStore().getBoolean(IPreferenceIds.SYNCVIEW_VIEW_SYNCINFO_IN_LABEL); + } + + private Image getCompareImage(Image base, int kind) { + switch (kind & SyncInfo.DIRECTION_MASK) { + case SyncInfo.OUTGOING : + kind = (kind & ~SyncInfo.OUTGOING) | SyncInfo.INCOMING; + break; + case SyncInfo.INCOMING : + kind = (kind & ~SyncInfo.INCOMING) | SyncInfo.OUTGOING; + break; + } + return compareConfig.getImage(base, kind); + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose() + */ + public void dispose() { + compareConfig.dispose(); + if (fgImageCache != null) { + Iterator it = fgImageCache.values().iterator(); + while (it.hasNext()) { + Image element = (Image) it.next(); + element.dispose(); + } + } + } + + /** + * Return the label provider that will return the text and image + * appropriate for the given model element. Subclasses are responsible for + * disposing of the label provider. + * @return the label provider that will return the text and image + * appropriate for the given model element + */ + protected abstract LabelProvider getModelLabelProvider(); + + /** + * Return the sync kind of the given element. This is used + * to determine how to decorate the image and label of the + * element. + * @param element the element being tested + * @return the sync kind of the given element + */ + protected abstract int getSyncKind(Object element); + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IDisposeListener.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IDisposeListener.java new file mode 100644 index 000000000..261b023c5 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IDisposeListener.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +/** + * Listener that, when registered with a synchronization context, gets invoked + * when the context is disposed. + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public interface IDisposeListener { + + /** + * The given context has been disposed. + */ + void contextDisposed(ISynchronizeOperationContext context); +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IMergeContext.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IMergeContext.java new file mode 100644 index 000000000..01f40c750 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IMergeContext.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.team.core.synchronize.SyncInfo; +import org.eclipse.team.core.synchronize.SyncInfoSet; + +/** + * Provides the context for an <code>IResourceMappingMerger</code> + * or a model specific synchronization view that supports merging. + * + * TODO: Need to have a story for folder merging + * <p> + * This interface is not intended to be implemented by clients. + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @see IResourceMappingMerger + * @since 3.2 + */ +public interface IMergeContext extends ISynchronizeOperationContext { + + /** + * Method that allows the model merger to signal that the file in question + * has been completely merged. Model mergers can call this method if they + * have transfered all changes from a remote file to a local file and wish + * to signal that the merge is done.This will allow repository providers to + * update the synchronization state of the file to reflect that the file is + * up-to-date with the repository. + * <p> + * Clients should not implement this interface but should instead subclass + * MergeContext. + * + * TODO: How are these batched? IWorkspace#run? + * + * @see MergeContext + * + * @param file the file that has been merged + * @param monitor a progress monitor + * @return a status indicating the results of the operation + */ + public abstract IStatus markAsMerged(IFile file, IProgressMonitor monitor); + + /** + * Method that can be called by the model merger to attempt a file-system + * level merge. This is useful for cases where the model merger does not + * need to do any special processing to perform the merge. By default, this + * method attempts to use an appropriate <code>IStreamMerger</code> to + * merge the files covered by the provided traversals. If a stream merger + * cannot be found, the text merger is used. If this behavior is not + * desired, sub-classes may override this method. + * <p> + * This method does a best-effort attempt to merge all the files covered + * by the provided traversals. Files that could not be merged will be + * indicated in the returned status. If the status returned has the code + * <code>MergeStatus.CONFLICTS</code>, the list of failed files can be + * obtained by calling the <code>MergeStatus#getConflictingFiles()</code> + * method. + * <p> + * Any resource changes triggered by this merge will be reported through the + * resource delta mechanism and the sync-info tree associated with this context. + * + * TODO: How do we handle folder removals generically? + * + * @see SyncInfoSet#addSyncSetChangedListener(ISyncInfoSetChangeListener) + * @see org.eclipse.core.resources.IWorkspace#addResourceChangeListener(IResourceChangeListener) + * + * @param infos + * the sync infos to be merged + * @param monitor + * a progress monitor + * @return a status indicating success or failure. A code of + * <code>MergeStatus.CONFLICTS</code> indicates that the file + * contain non-mergable conflicts and must be merged manually. + * @throws CoreException if an error occurs + */ + public IStatus merge(SyncInfoSet infos, IProgressMonitor monitor) throws CoreException; + + /** + * Method that can be called by the model merger to attempt a file level + * merge. This is useful for cases where the model merger does not need to + * do any special processing to perform the merge. By default, this method + * attempts to use an appropriate <code>IStreamMerger</code> to perform the + * merge. If a stream merger cannot be found, the text merger is used. If this behavior + * is not desired, sub-classes may override this method. + * + * @param file the file to be merged + * @param monitor a progress monitor + * @return a status indicating success or failure. A code of + * <code>MergeStatus.CONFLICTS</code> indicates that the file contain + * non-mergable conflicts and must be merged manually. + * @see org.eclipse.team.ui.mapping.IMergeContext#merge(org.eclipse.core.resources.IFile, org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus merge(SyncInfo info, IProgressMonitor monitor); + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IResourceMappingMerger.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IResourceMappingMerger.java new file mode 100644 index 000000000..4de3e0075 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IResourceMappingMerger.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import org.eclipse.core.runtime.*; + +/** + * The purpose of this interface is to provide support for model level + * auto-merging. It is helpful in the + * cases where a file may contain multiple model elements or a model element + * consists of multiple files. It can also be used for cases where there is a + * one-to-one mapping between model elements and files, although + * <code>IStreamMerger</code> can also be used in that case. + * + * Clients should group resource mappings by model provider and + * then attempt to obtain a merger from the model provider + * using the adaptable mechanism as follows: + * + * <pre> + * Object o = mapping.getModelProvider().getAdapter(IResourceMappingMerger.class); + * if (o instanceof IResourceMappingMerger.class) { + * IResourceMappingMerger merger = (IResourceMappingMerger)o; + * ... + * } + * </pre> + * + * If the model provider of the resource mappings does not adapt to + * <code>IResourceMappingMerger</code>, clients can obtain the merger + * from the Resources model provider {@link org.eclipse.core.resources.mapping.ResourceModelProvider} + * and use that. + * TODO: This is OK for now but will need to change + * + * @see org.eclipse.compare.IStreamMerger + * @see org.eclipse.core.resources.mapping.ResourceMapping + * @see org.eclipse.core.resources.mapping.ModelProvider + * @see org.eclipse.team.ui.mapping.IMergeContext + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public interface IResourceMappingMerger { + + /** + * Attempt to automatically merge the mappings of the merge context(<code>MergeContext#getMappings()</code>). + * The merge context provides access to the out-of-sync resources (<code>MergeContext#getSyncInfoTree()</code>) + * associated with the mappings to be merged. The set of provided mappings + * may come from multiple model providers. A particular implementation of + * this interface should only merge the mappings associated with their model + * provider. Also, the set of resources may contain additional resources + * that are not part of the mappings being merged. Implementors of this + * interface should use the mappings to determine which resources to merge + * and what additional semantics can be used to attempt the merge. + * <p> + * The type of merge to be performed depends on what is returned by the + * <code>MergeContext#getType()</code> method. If the type is + * <code>MergeContext.TWO_WAY</code> the merge will replace the local + * contents with the remote contents, ignoring any local changes. For + * <code>THREE_WAY</code>, the base is used to attempt to merge remote + * changes with local changes. + * <p> + * If merging was not possible for one or more of the mappings to which this + * merge applies, these mappings should be returned in an + * <code>MergeStatus</code> whose code is + * <code>MergeStatus.CONFLICTS</code> and which provides access to the + * mappings which could not be merged. Note that it is up to the model to + * decide whether it wants to break one of the provided resource mappings + * into several sub-mappings and attempt auto-merging at that level. + * + * @param mappings the set of resource mappings being merged + * @param mergeContext a context that provides access to the resources + * involved in the merge. The context must not be + * <code>null</code>. + * @param monitor a progress monitor + * @return a status indicating the results of the operation. A code of + * <code>MergeStatus.CONFLICTS</code> indicates that some or all + * of the resource mappings could not be merged. The mappings that + * were not merged are available using + * <code>MergeStatus#getConflictingMappings()</code> + * @throws CoreException if errors occurred + */ + public IStatus merge(IMergeContext mergeContext, + IProgressMonitor monitor) throws CoreException; + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IResourceMappingOperationInput.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IResourceMappingOperationInput.java new file mode 100644 index 000000000..08b749651 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/IResourceMappingOperationInput.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.resources.mapping.ResourceTraversal; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.team.ui.synchronize.ISynchronizeScope; + +/** + * Interface which defines the protocol for translating + * a set of <code>ResourceMapping</code> objects representing + * a view selection into the complete set of resources to + * be operated on. + * <p> + * This interface is not intended to be implemented by clients + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @see org.eclipse.core.resources.mapping.ResourceMapping + * + * @since 3.2 + */ +public interface IResourceMappingOperationInput { + + /** + * Return the set of mappings that were selected + * when the operation was launched. These mappings + * are used to seed the input determination process. + * @return the set of mappings that were selected + * when the operation was launched + */ + public ResourceMapping[] getSeedMappings(); + + /** + * Calculate the set of mappings to be operated on. + * This method must be called before <code>getInputMappings</code> + * or <code>getInputTraversals</code>. + * + * @param monitor a progress monitor + * @throws CoreException + */ + public void buildInput(IProgressMonitor monitor) throws CoreException; + + /** + * Return the complete set of mappings to be operated on. + * This method should only be invoked after <code>buildInput</code> + * is called. + * @return the complete set of mappings to be operated on + */ + public ResourceMapping[] getInputMappings(); + + /** + * Return the set of traversals that cover the input + * resource mappings. + * This method should only be invoked after <code>buildInput</code> + * is called. + * @return the complete set of mappings to be operated on + */ + public ResourceTraversal[] getInputTraversals(); + + /** + * Return the traversals that cover the given mapping. + * @param mapping a resource mapping being operated on + * @return the traversals that cover the given resource mapping + * (or <code>null</code> if the mapping is not contained in the input) + */ + public ResourceTraversal[] getTraversal(ResourceMapping mapping); + + /** + * Return whether the input has additional mappings added to the + * seed mappings. + * This method should only be invoked after <code>buildInput</code> + * is called. + * @return whether the input has additional mappings added to the + * seed mappings + */ + public boolean hasAdditionalMappings(); + + public ModelProvider[] getModelProviders(); + + public ResourceMapping[] getMappings(ModelProvider provider); + + public ISynchronizeScope asSynchronizationScope(); +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ISynchronizationContext.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ISynchronizationContext.java new file mode 100644 index 000000000..265eaff4e --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ISynchronizationContext.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceTraversal; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener; +import org.eclipse.team.core.synchronize.SyncInfo; +import org.eclipse.team.core.synchronize.SyncInfoSet; +import org.eclipse.team.core.synchronize.SyncInfoTree; +import org.eclipse.team.ui.synchronize.ISynchronizeScope; + +/** + * Allows a model provider to build a view of their model that includes + * synchronization information with a remote location (usually a repository). + * <p> + * The scope of the context is defined when the context is created. The creator + * of the scope may affect changes on the scope which will result in property + * change events from the scope and may result in sync-info change events from + * the sync-info tree. Clients should note that it is possible that a change in + * the scope will result in new out-of-sync resources being covered by the scope + * but not result in a sync-info change event from the sync-info tree. This can + * occur because the set may already have contained the out-of-sync resource + * with the understanding that the client would have ignored it. Consequently, + * clients should listen to both sources in order to guarantee that they update + * any dependent state appropriately. + * <p> + * This interface is not intended to be implemented by clients. + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public interface ISynchronizationContext extends ITeamViewerContext { + + /** + * Synchronization type constant that indicates that + * context is a two-way synchronization. + */ + public final static String TWO_WAY = "two-way"; //$NON-NLS-1$ + + /** + * Synchronization type constant that indicates that + * context is a three-way synchronization. + */ + public final static String THREE_WAY = "three-way"; //$NON-NLS-1$ + + /** + * Return the scope of this synchronization context. The scope determines + * the set of resources to which the context applies. Changes in the scope + * may result in changes to the sync-info available in the tree of this + * context. + * + * @return the set of mappings for which this context applies. + */ + public ISynchronizeScope getScope(); + + /** + * Return a tree that contains <code>SyncInfo</code> nodes for resources + * that are out-of-sync. The tree will contain sync-info for any out-of-sync + * resources that are within the scope of this context. The tree + * may include additional out-of-sync resources, which should be ignored by + * the client. Clients can test for inclusion using the method + * {@link ISynchronizeScope#contains(IResource)}. + * + * @return a tree that contains a <code>SyncInfo</code> node for any + * resources that are out-of-sync. + */ + public SyncInfoTree getSyncInfoTree(); + + /** + * Returns synchronization info for the given resource, or <code>null</code> + * if there is no synchronization info because the resource is not a + * candidate for synchronization. + * <p> + * Note that sync info may be returned for non-existing or for resources + * which have no corresponding remote resource. + * </p> + * <p> + * This method will be quick. If synchronization calculation requires content from + * the server it must be cached when the context is created or refreshed. A client should + * call refresh before calling this method to ensure that the latest information + * is available for computing the sync state. + * </p> + * @param resource the resource of interest + * @return sync info + * @throws CoreException + */ + public SyncInfo getSyncInfo(IResource resource) throws CoreException; + + /** + * Return the synchronization type. A type of <code>TWO_WAY</code> + * indicates that the synchronization information (i.e. + * <code>SyncInfo</code>) associated with the context will also be + * two-way (i.e. there is only a remote but no base involved in the + * comparison used to determine the synchronization state of resources. A + * type of <code>THREE_WAY</code> indicates that the synchronization + * information will be three-way and include the local, base (or ancestor) + * and remote. + * + * @return the type of merge to take place + * + * @see org.eclipse.team.core.synchronize.SyncInfo + */ + public String getType(); + + /** + * Dispose of the synchronization context. This method should be + * invoked by clients when the context is no longer needed. + */ + public void dispose(); + + /** + * Refresh the context in order to update the sync-info to include the + * latest remote state. any changes will be reported through the change + * listeners registered with the sync-info tree of this context. Changes to + * the set may be triggered by a call to this method or by a refresh + * triggered by some other source. + * + * @see SyncInfoSet#addSyncSetChangedListener(ISyncInfoSetChangeListener) + * @see org.eclipse.team.core.synchronize.ISyncInfoTreeChangeEvent + * + * @param traversals the resource traversals which indicate which resources + * are to be refreshed + * @param flags additional refresh behavior. For instance, if + * <code>RemoteResourceMappingContext.FILE_CONTENTS_REQUIRED</code> + * is one of the flags, this indicates that the client will be + * accessing the contents of the files covered by the traversals. + * <code>NONE</code> should be used when no additional behavior + * is required + * @param monitor a progress monitor, or <code>null</code> if progress + * reporting is not desired + * @throws CoreException if the refresh fails. Reasons include: + * <ul> + * <li>The server could not be contacted for some reason (e.g. + * the context in which the operation is being called must be + * short running). The status code will be + * SERVER_CONTACT_PROHIBITED. </li> + * </ul> + */ + public void refresh(ResourceTraversal[] traversals, int flags, IProgressMonitor monitor) throws CoreException; + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ISynchronizeOperationContext.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ISynchronizeOperationContext.java new file mode 100644 index 000000000..dbe6cd6ca --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ISynchronizeOperationContext.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +/** + * A synchronization context that allows clients to cache operation + * state for the duration of the operation. When the context is disposed, + * the cache will be cleared. + * <p> + * This interface is not intended to be implemented by clients. Clients + * should instead subclass <@link org.eclipse.team.ui.mapping.SynchronizeOperationContext} + * + * @see org.eclipse.team.ui.mapping.SynchronizeOperationContext + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public interface ISynchronizeOperationContext extends ISynchronizationContext { + + /** + * Cache the given property with this context. + * @param name the property name that uniquely identifies the property + * @param value the value to be cached. + */ + void addProperty(String name, Object value); + + /** + * Retrieve a property that has been cached with the context + * @param name the name of the property + * @return the object associated with the property name or <code>null</code> + */ + Object getProperty(String name); + + /** + * Remove the named property from the context + * @param name the property name + */ + void removeProperty(String name); + + /** + * Add a listener to the context that will receive notification + * when the context is disposed. Adding a listener that has already + * been added has no effect. + * @param listener the listener to add + */ + void addDisposeListener(IDisposeListener listener); + + /** + * Remove the listener. Removing a listener that is not registered + * has no effect. + * @param listener the listener to remove + */ + void removeDisposeListener(IDisposeListener listener); +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ITeamViewerContext.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ITeamViewerContext.java new file mode 100644 index 000000000..dd6115160 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ITeamViewerContext.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.resources.mapping.ResourceMapping; + +/** + * A context determined by Team providers and passed to model views + * in order to display a subset of a model that is involved in a team + * operation + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public interface ITeamViewerContext { + + public static final String ALL_MAPPINGS = ""; + + /** + * Return the model providers that have mappings + * in this context. + * @return the model providers that have mappings + * in this context + */ + public ModelProvider[] getModelProviders(); + + /** + * Return the set of resource mappings associated with + * the given model provider affected by the + * team operation that provided the context. If all + * resource mappings are desired, pass <code>ALL_MAPPINGS</code> + * as the model provider id. + * @param modelProviderId the model provider id. + * @return a set of resource mappings + */ + public ResourceMapping[] getResourceMappings(String modelProviderId); + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/MergeContext.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/MergeContext.java new file mode 100644 index 000000000..ed6351a9e --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/MergeContext.java @@ -0,0 +1,399 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import java.io.*; +import java.util.*; + +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.IStreamMerger; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentDescription; +import org.eclipse.osgi.util.NLS; +import org.eclipse.team.core.synchronize.*; +import org.eclipse.team.core.variants.IResourceVariant; +import org.eclipse.team.internal.core.TeamPlugin; +import org.eclipse.team.internal.ui.TeamUIPlugin; + +/** + * Provides the context for an <code>IResourceMappingMerger</code>. + * It provides access to the ancestor and remote resource mapping contexts + * so that resource mapping mergers can attempt head-less auto-merges. + * The ancestor context is only required for merges while the remote + * is required for both merge and replace. + * + * TODO: Need to have a story for folder merging + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @see IResourceMappingMerger + * @since 3.2 + */ +public abstract class MergeContext extends SynchronizeOperationContext implements IMergeContext { + + private static final String TXT_EXTENTION = "txt"; //$NON-NLS-1$ + + private final String type; + private final SyncInfoTree tree; + + /** + * Create a merge context. + * @param type + */ + protected MergeContext(String type, SyncInfoTree tree, IResourceMappingOperationInput input) { + super(input); + this.type = type; + this.tree = tree; + } + + /** + * Return the type of merge to take place. A + * type of <code>TWO_WAY</code> indicates that + * the local contents are to be replaced with + * the remote contents while a type of <code>THREE_WAY</code> + * indicates that the remote changes should be merged + * with the local changes. + * @return the type of merge to take place + */ + public final String getType() { + return type; + } + + /** + * Return a tree that contains a <code>SyncInfo</code> + * node for any resources that are out-of-sync. + * @return a tree that contains a <code>SyncInfo</code> + * node for any resources that are out-of-sync. + */ + public final SyncInfoTree getSyncInfoTree() { + return tree; + } + + /** + * Method that allows the model merger to signal that the file in question + * has been completely merged. Model mergers can call this method if they + * have transfered all changes from a remote file to a local file and wish + * to signal that the merge is done.This will allow repository providers to + * update the synchronization state of the file to reflect that the file is + * up-to-date with the repository. + * + * @param file the file that has been merged + * @param monitor a progress monitor + * @return a status indicating the results of the operation + */ + public abstract IStatus markAsMerged(IFile file, IProgressMonitor monitor); + + /** + * Method that can be called by the model merger to attempt a file-system + * level merge. This is useful for cases where the model merger does not + * need to do any special processing to perform the merge. By default, this + * method attempts to use an appropriate <code>IStreamMerger</code> to + * merge the files covered by the provided traversals. If a stream merger + * cannot be found, the text merger is used. If this behavior is not + * desired, sub-classes may override this method. + * <p> + * This method does a best-effort attempt to merge all the files covered + * by the provided traversals. Files that could not be merged will be + * indicated in the returned status. If the status returned has the code + * <code>MergeStatus.CONFLICTS</code>, the list of failed files can be + * obtained by calling the <code>MergeStatus#getConflictingFiles()</code> + * method. + * <p> + * Any resource changes triggered by this merge will be reported through the + * resource delta mechanism and the sync-info tree associated with this context. + * + * TODO: How do we handle folder removals generically? + * + * @see SyncInfoSet#addSyncSetChangedListener(ISyncInfoSetChangeListener) + * @see org.eclipse.core.resources.IWorkspace#addResourceChangeListener(IResourceChangeListener) + * + * @param infos + * the array of sync info to be merged + * @param monitor + * a progress monitor + * @return a status indicating success or failure. A code of + * <code>MergeStatus.CONFLICTS</code> indicates that the file + * contain non-mergable conflicts and must be merged manually. + * @throws CoreException if an error occurs + */ + public IStatus merge(SyncInfoSet infos, IProgressMonitor monitor) throws CoreException { + List failedFiles = new ArrayList(); + for (Iterator iter = infos.iterator(); iter.hasNext();) { + SyncInfo info = (SyncInfo) iter.next(); + IStatus s = merge(info, monitor); + if (!s.isOK()) { + if (s.getCode() == MergeStatus.CONFLICTS) { + failedFiles.addAll(Arrays.asList(((MergeStatus)s).getConflictingFiles())); + } else { + return s; + } + } + } + if (failedFiles.isEmpty()) { + return Status.OK_STATUS; + } else { + return new MergeStatus(TeamPlugin.ID, "Could not merge all files", (IFile[]) failedFiles.toArray(new IFile[failedFiles.size()])); + } + } + + /** + * Method that can be called by the model merger to attempt a file level + * merge. This is useful for cases where the model merger does not need to + * do any special processing to perform the merge. By default, this method + * attempts to use an appropriate <code>IStreamMerger</code> to perform the + * merge. If a stream merger cannot be found, the text merger is used. If this behavior + * is not desired, sub-classes may override this method. + * + * @param file the file to be merged + * @param monitor a progress monitor + * @return a status indicating success or failure. A code of + * <code>MergeStatus.CONFLICTS</code> indicates that the file contain + * non-mergable conflicts and must be merged manually. + * @see org.eclipse.team.ui.mapping.MergeContext#merge(org.eclipse.core.resources.IFile, org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus merge(SyncInfo info, IProgressMonitor monitor) { + IResource r = info.getLocal(); + if (r.getType() != IResource.FILE) + return Status.OK_STATUS; + IFile file = (IFile)r; + try { + if (info.getComparator().isThreeWay()) { + int direction = SyncInfo.getDirection(info.getKind()); + if (direction == SyncInfo.OUTGOING) { + // There's nothing to do so return OK + return Status.OK_STATUS; + } + if (direction == SyncInfo.INCOMING) { + // Just copy the stream since there are no conflicts + return performReplace(info, monitor); + } + // direction == SyncInfo.CONFLICTING + int type = SyncInfo.getChange(info.getKind()); + if (type == SyncInfo.DELETION) { + // Nothing needs to be done although subclasses + markAsMerged(file, monitor); + return Status.OK_STATUS; + } + // type == SyncInfo.CHANGE + IResourceVariant base = info.getBase(); + IResourceVariant remote = info.getRemote(); + if (base == null || remote == null || !file.exists()) { + // Nothing we can do so return a conflict status + // TODO: Should we handle the case where the local and remote have the same contents for a conflicting addition? + return new MergeStatus(TeamUIPlugin.ID, NLS.bind("Conflicting change could not be merged: {0}", new String[] { file.getFullPath().toString() }), new IFile[] { file }); + } + // We have a conflict, a local, base and remote so we can do + // a three-way merge + return performThreeWayMerge(info, monitor); + } else { + return performReplace(info, monitor); + } + } catch (CoreException e) { + return new Status(IStatus.ERROR, TeamPlugin.ID, MergeStatus.INTERNAL_ERROR, NLS.bind("Merge of {0} failed due to an internal error.", new String[] { file.getFullPath().toString() }), e); + } + } + + /* + * Replace the local contents with the remote contents. + * The local resource must be a file. + */ + private IStatus performReplace(SyncInfo info, IProgressMonitor monitor) throws CoreException { + IFile file = (IFile)info.getLocal(); + IResourceVariant remote = info.getRemote(); + if (remote == null && file.exists()) { + file.delete(false, true, monitor); + } else if (remote != null) { + InputStream stream = remote.getStorage(monitor).getContents(); + stream = new BufferedInputStream(stream); + try { + if (file.exists()) { + file.setContents(stream, false, true, monitor); + } else { + file.create(stream, false, monitor); + } + } finally { + try { + stream.close(); + } catch (IOException e) { + // Ignore + } + } + } + markAsMerged(file, monitor); + return Status.OK_STATUS; + } + + /* + * Perform a three-way merge on the given sync-info. + * The local resource must be a file and all three + * resources (local, base, remote) must exist. + */ + private IStatus performThreeWayMerge(SyncInfo info, IProgressMonitor monitor) throws CoreException { + IFile file = (IFile)info.getLocal(); + IContentDescription contentDescription = file.getContentDescription(); + IStreamMerger merger = null; + if (contentDescription != null && contentDescription.getContentType() != null) { + merger = CompareUI.createStreamMerger(contentDescription.getContentType()); + } else { + String fileExtension = file.getFileExtension(); + if (fileExtension != null) + merger = CompareUI.createStreamMerger(fileExtension); + } + // If we couldn't find a registered merger, fallback to text + // since we know we have one of those registered. + if (merger == null) + merger = CompareUI.createStreamMerger(TXT_EXTENTION); + if (merger == null) + return new Status(IStatus.ERROR, TeamPlugin.ID, MergeStatus.INTERNAL_ERROR, NLS.bind("Auto-merge support for {0} is not available.", new String[] { file.getFullPath().toString() }), null); + return merge(merger, info, monitor); + } + + /* + * Perform a three-way merge on the given sync-info using the given + * stream merger. The local resource must be a file and all three + * resources (local, base, remote) must exist. + */ + private IStatus merge(IStreamMerger merger, SyncInfo info, IProgressMonitor monitor) throws CoreException { + + // Get the file involved + IFile file = (IFile)info.getLocal(); + + // Define all the input streams here so we can ensure they get closed + InputStream ancestorStream = null; + InputStream remoteStream = null; + InputStream targetStream = null; + + try { + + + // Get the ancestor stream and encoding + IResourceVariant base = info.getBase(); + IStorage s = base.getStorage(monitor); + String ancestorEncoding = null; + if (s instanceof IEncodedStorage) { + IEncodedStorage es = (IEncodedStorage) s; + ancestorEncoding = es.getCharset(); + } + if (ancestorEncoding == null) { + ancestorEncoding = file.getCharset(); + } + ancestorStream = new BufferedInputStream(s.getContents()); + + // Get the remote stream and encoding + IResourceVariant remote = info.getRemote(); + s = remote.getStorage(monitor); + String remoteEncoding = null; + if (s instanceof IEncodedStorage) { + IEncodedStorage es = (IEncodedStorage) s; + remoteEncoding = es.getCharset(); + } + if (remoteEncoding == null) { + remoteEncoding = file.getCharset(); + } + remoteStream = new BufferedInputStream(s.getContents()); + + // Get the local (target) stream and encoding + targetStream = file.getContents(); + String targetEncoding = file.getCharset(); + IStatus status; + OutputStream output = getTempOutputStream(file); + try { + status = merger.merge(output, targetEncoding, ancestorStream, ancestorEncoding, targetStream, targetEncoding, remoteStream, remoteEncoding, monitor); + if (status.isOK()) { + file.setContents(getTempInputStream(file, output), false, true, monitor); + markAsMerged(file, monitor); + } + } finally { + disposeTempOutputStream(file, output); + } + return status; + } finally { + try { + if (ancestorStream != null) + ancestorStream.close(); + } catch (IOException e) { + // Ignore + } + try { + if (remoteStream != null) + remoteStream.close(); + } catch (IOException e) { + // Ignore + } + try { + if (targetStream != null) + targetStream.close(); + } catch (IOException e) { + // Ignore + } + } + } + + private InputStream getTempInputStream(IFile file, OutputStream output) throws CoreException { + if (output instanceof ByteArrayOutputStream) { + ByteArrayOutputStream baos = (ByteArrayOutputStream) output; + return new ByteArrayInputStream(baos.toByteArray()); + } + // We created a temporary file so we need to open an input stream on it + try { + // First make sure the output stream is closed + if (output != null) + output.close(); + } catch (IOException e) { + // Ignore + } + File tmpFile = getTempFile(file); + try { + return new BufferedInputStream(new FileInputStream(tmpFile)); + } catch (FileNotFoundException e) { + throw new CoreException(new Status(IStatus.ERROR, TeamPlugin.ID, MergeStatus.INTERNAL_ERROR, NLS.bind("Could not read from temporary file {0}: {1}", new String[] { tmpFile.getAbsolutePath(), e.getMessage() }), e)); + } + } + + private void disposeTempOutputStream(IFile file, OutputStream output) { + if (output instanceof ByteArrayOutputStream) + return; + // We created a temporary file so we need to clean it up + try { + // First make sure the output stream is closed + // so that file deletion will not fail because of that. + if (output != null) + output.close(); + } catch (IOException e) { + // Ignore + } + File tmpFile = getTempFile(file); + if (tmpFile.exists()) + tmpFile.delete(); + } + + private OutputStream getTempOutputStream(IFile file) throws CoreException { + File tmpFile = getTempFile(file); + if (tmpFile.exists()) + tmpFile.delete(); + try { + return new BufferedOutputStream(new FileOutputStream(tmpFile)); + } catch (FileNotFoundException e) { + TeamPlugin.log(IStatus.ERROR, NLS.bind("Could not open temporary file {0} for writing: {1}", new String[] { tmpFile.getAbsolutePath(), e.getMessage() }), e); + return new ByteArrayOutputStream(); + } + } + + private File getTempFile(IFile file) { + return TeamPlugin.getPlugin().getStateLocation().append(".tmp").append(file.getName() + ".tmp").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/MergeStatus.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/MergeStatus.java new file mode 100644 index 000000000..4c20a2ab9 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/MergeStatus.java @@ -0,0 +1,88 @@ +package org.eclipse.team.ui.mapping; + +import org.eclipse.compare.IStreamMerger; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +/** + * A special status that is returned when the return code + * of the <code>merge</code> method is <code>CONFLICTS</code>. + * It is possible that there were problems that caused the + * auto-merge to fail. In that case, the implementor of + * <code>IResourceMappingMerger</code> can return a multi-status + * in which one of the children is a <code>MergeStatus</code> and + * the others describe other problems that were encountered. + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @see org.eclipse.team.ui.mapping.IResourceMappingMerger + * + * @since 3.2 + */ +public final class MergeStatus extends Status { + + private ResourceMapping[] conflictingMappings; + private IFile[] conflictingFiles; + + /** + * Create a merge status for reporting that some of the resource mappings + * for which a merge was attempted were not auto-mergable. + * @param pluginId the plugin id + * @param message the message for the status + * @param conflictingMappings the mappings which were not auto-mergable + */ + public MergeStatus(String pluginId, String message, ResourceMapping[] conflictingMappings) { + super(IStatus.ERROR, pluginId, CONFLICTS, message, null); + this.conflictingMappings = conflictingMappings; + } + + /** + * Create a merge status for reporting that some of the files + * for which a merge was attempted were not auto-mergable. + * @param pluginId the plugin id + * @param message the message for the status + * @param files the files which were not auto-mergable + */ + public MergeStatus(String pluginId, String message, IFile[] files) { + super(IStatus.ERROR, pluginId, CONFLICTS, message, null); + this.conflictingFiles = files; + } + + /** + * Indicates that a change conflict prevented some or all of the resource + * mappings to be merged (value <code>1</code>). When this code is + * returned, the status must be of type + * <code>MergeStatus</code> and must contain the list of all + * resource mappings for which a manual merge is required. + */ + public static final int CONFLICTS = IStreamMerger.CONFLICT; + + /** + * Status code describing an internal error (value <code>2</code>). + * The status return is not required to be of type <code>MergeStatus</code> + * for internal errors. + */ + public static final int INTERNAL_ERROR= IStreamMerger.INTERNAL_ERROR; + + /** + * Returns the set of resource mappings for which an auto-merge was + * not performed. The client should present the mappings to the user + * in a manner that will allow the user to perform a manual merges. + * @return the set of resource mappings for which an auto-merge was + * not performed. + */ + public ResourceMapping[] getConflictingMappings() { + return conflictingMappings; + } + + public IFile[] getConflictingFiles() { + return conflictingFiles; + } +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ResourceMappingMergeOperation.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ResourceMappingMergeOperation.java new file mode 100644 index 000000000..12e601e19 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ResourceMappingMergeOperation.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.runtime.*; +import org.eclipse.team.core.TeamException; +import org.eclipse.team.internal.ui.Policy; +import org.eclipse.ui.IWorkbenchPart; + +/** + * The steps of an optimistic merge operation are: + * <ol> + * <li>Obtain the selection to be operated on. + * <li>Determine the projection of the selection onto resources + * using resource mappings and traversals. + * <ul> + * <li>this will require traversals using both the ancestor and remote + * for three-way merges. + * <li>for model providers with registered merger, mapping set need + * not be expanded (this is tricky if one of the model providers doesn't + * have a merge but all others do). + * <li>if the model does not have a custom merger, ensure that additional + * mappings are included (i.e. for many model elements to one resource case) + * </ul> + * <li>Create a MergeContext for the merge + * <ul> + * <li>Determine the synchronization state of all resources + * covered by the input. + * <li>Pre-fetch the required contents. + * </ul> + * <li>Obtain and invoke the merger for each provider + * <ul> + * <li>This will auto-merge as much as possible + * <li>If everything was merged, cleanup and stop + * <li>Otherwise, a set of un-merged resource mappings is returned + * </ul> + * <li>Delegate manual merge to the model provider + * <ul> + * <li>This hands off the context to the manual merge + * <li>Once completed, the manual merge must clean up + * </ul> + * </ol> + * + * <p> + * Handle multiple model providers where one extends all others by using + * the top-most model provider. The assumption is that the model provider + * will delegate to lower level model providers when appropriate. + * <p> + * Special case to support sub-file merges. + * <ul> + * <li>Restrict when sub-file merging is supported + * <ul> + * <li>Only one provider involved (i.e. consulting participants results + * in participants that are from the model provider or below). + * <li>The provider has a custom auto and manual merger. + * </ul> + * <li>Prompt to warn when sub-file merging is not possible. + * <li>Need to display the additional elements that will be affected. + * This could be done in a diff tree or some other view. It needs to + * consider incoming changes including additions. + * </ul> + * <p> + * Special case to handle conflicting model providers. + * <ul> + * <li>Prompt user to indicate the conflict + * <li>Allow user to exclude one of the models? + * <li>Allow use to choose order of evaluation? + * <li>Support tabbed sync view + * </ul> + * <p> + * TODO: What about support for down grading a merge. That is, the user wants + * to perform the merge at the file level even though a higher level model owns + * the files. We could provide a preference that the user can set to perform + * the merge at the level selected and not involve participants. + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public abstract class ResourceMappingMergeOperation extends ResourceMappingOperation { + + protected ResourceMappingMergeOperation(IWorkbenchPart part, IResourceMappingOperationInput input) { + super(part, input); + } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.mapping.ResourceMappingOperation#execute(org.eclipse.core.runtime.IProgressMonitor) + */ + protected void execute(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + try { + IMergeContext context = buildMergeContext(monitor); + ModelProvider[] providers = getInput().getModelProviders(); + List failedMerges = new ArrayList(); + for (int i = 0; i < providers.length; i++) { + ModelProvider provider = providers[i]; + if (!performMerge(provider, context, monitor)) { + failedMerges.add(provider); + } + } + if (failedMerges.isEmpty()) { + context.dispose(); + } else { + requiresManualMerge((ModelProvider[]) failedMerges.toArray(new ModelProvider[failedMerges.size()]), context); + } + } catch (CoreException e) { + throw new InvocationTargetException(e); + } + } + + /** + * One or more of the model elements for the given providers + * requires a manual merge. When the manual merge is + * @param providers the providers + * @param context the merge context + * @throws CoreException + */ + protected abstract void requiresManualMerge(ModelProvider[] providers, IMergeContext context) throws CoreException; + + /** + * Build and initialize a merge context for the input of this operation. + * @param monitor a progress monitor + * @return a merge context for merging the mappings of the input + */ + protected abstract IMergeContext buildMergeContext(IProgressMonitor monitor); + + /** + * Merge all the mappings that come from the given provider. By default, + * an automatic merge is attempted. After this, a manual merge (i.e. with user + * intervention) is attempted on any mappings that could not be merged + * automatically. + * @param provider the model provider + * @param mappings the mappings to be merged + * @param monitor a progress monitor + * @throws CoreException + */ + protected boolean performMerge(ModelProvider provider, IMergeContext mergeContext, IProgressMonitor monitor) throws CoreException { + try { + monitor.beginTask(null, 100); + IStatus status = performAutoMerge(provider, mergeContext, Policy.subMonitorFor(monitor, 95)); + if (!status.isOK()) { + if (status.getCode() == MergeStatus.CONFLICTS) { + return false; + } else { + throw new TeamException(status); + } + } + } finally { + monitor.done(); + } + return true; + } + + /** + * Attempt to merge automatically. The returned status will indicate which + * mappings could not be merged automatically. + * @param provider the provider for the mappings being merged + * @param mergeContext the context for the merge + * @param monitor a progress monitor + * @return a status indicating success or failure. A failure status + * will be a MergeStatus that includes the mappings that could not be merged. + * @throws CoreException if errors occurred + */ + protected IStatus performAutoMerge(ModelProvider provider, IMergeContext mergeContext, IProgressMonitor monitor) throws CoreException { + IResourceMappingMerger merger = getMerger(provider); + IStatus status = merger.merge(mergeContext, monitor); + return status; + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ResourceMappingOperation.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ResourceMappingOperation.java new file mode 100644 index 000000000..882ac1540 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ResourceMappingOperation.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.*; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.team.internal.ui.dialogs.AdditionalMappingsDialog; +import org.eclipse.team.internal.ui.mapping.DefaultResourceMappingMerger; +import org.eclipse.team.ui.TeamOperation; +import org.eclipse.ui.IWorkbenchPart; + +/** + * Here's a summary of the input determination scheme + * <ol> + * <li>Obtain selected mappings + * <li>Project mappings onto resources using the appropriate + * context(s) in order to obtain a set of ResourceTraverals + * <li>Determine what model providers are interested in the targeted resources + * <li>From those model providers, obtain the set of affected resource mappings + * <li>If the original set is the same as the new set, we are done. + * <li>if the set differs from the original selection, rerun the mapping process + * for any new mappings + * <ul> + * <li>Only need to query model providers for mappings for new resources + * <li>If new mappings are obtained, + * ask model provider to compress the mappings? + * <li>keep repeating until no new mappings or resources are added + * </ul> + * <li>Use model provider relationships to result? + * <li>Display the original set and the new set with an explanation + * <ul> + * <li>The original set and final set may involve mappings from + * multiple providers. + * <li>The number of providers can be reduced by assuming that + * extending models can display the elements of extended models. + * Then we are only left with conflicting models. + * <li>Could use a content provider approach a.k.a. Common Navigator + * or component based approach + * </ul> + * </ol> + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public abstract class ResourceMappingOperation extends TeamOperation { + + private final IResourceMappingOperationInput input; + + /** + * Create a resource mapping based operation + * @param part the workspace part from which the operation was launched + * @param input the input to the operation (which must have already been built by + * invoking <code>buildInput</code>. + */ + protected ResourceMappingOperation(IWorkbenchPart part, IResourceMappingOperationInput input) { + super(part); + this.input = input; + } + + public void run(IProgressMonitor monitor) throws InvocationTargetException, + InterruptedException { + buildInput(monitor); + execute(monitor); + } + + /** + * Adjust the input of the operation according to the selected + * resource mappings and the set of interested participants + * @param monitor + */ + protected void buildInput(IProgressMonitor monitor) throws InvocationTargetException { + try { + input.buildInput(monitor); + if (input.hasAdditionalMappings()) { + promptForInputChange(monitor); + } + } catch (CoreException e) { + throw new InvocationTargetException(e); + } + } + + /** + * Prompt the user to inform them that additional resource mappings + * have been included in the operations. + * @param monitor a progress monitor + * @throws OperationCanceledException if the user choose to cancel + */ + protected void promptForInputChange(IProgressMonitor monitor) { + showAllMappings(input.getSeedMappings(), input.getInputMappings()); + } + + private void showAllMappings(final ResourceMapping[] selectedMappings, final ResourceMapping[] allMappings) { + final boolean[] canceled = new boolean[] { false }; + getShell().getDisplay().syncExec(new Runnable() { + public void run() { + AdditionalMappingsDialog dialog = new AdditionalMappingsDialog(getShell(), "Participating Elements", selectedMappings, new TeamViewerContext(allMappings)); + int result = dialog.open(); + canceled[0] = result != Dialog.OK; + } + + }); + + if (canceled[0]) { + throw new OperationCanceledException(); + } + } + + protected abstract void execute(IProgressMonitor monitor) throws InvocationTargetException, + InterruptedException; + + /** + * Return the auto-merger associated with the given model provider + * view the adaptable mechanism. + * If the model provider does not have a merger associated with + * it, a default merger that performs the merge at the file level + * is returned. + * @param provider the model provider of the elements to be merged + * @return a merger + */ + protected IResourceMappingMerger getMerger(ModelProvider provider) { + Object o = provider.getAdapter(IResourceMappingMerger.class); + if (o instanceof IResourceMappingMerger) { + return (IResourceMappingMerger) o; + } + return new DefaultResourceMappingMerger(provider, getInput()); + } + + public IResourceMappingOperationInput getInput() { + return input; + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ResourceMappingOperationInput.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ResourceMappingOperationInput.java new file mode 100644 index 000000000..59dc4fd4b --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/ResourceMappingOperationInput.java @@ -0,0 +1,251 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.IModelProviderDescriptor; +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.resources.mapping.ResourceMappingContext; +import org.eclipse.core.resources.mapping.ResourceTraversal; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.team.internal.ui.Policy; +import org.eclipse.team.internal.ui.TeamUIPlugin; +import org.eclipse.team.internal.ui.mapping.SimpleResourceMappingOperationInput; + +/** + * Transform the set of selected resource mappings into the + * complete set of resource mappings affected by the operation. + * <p> + * Here's a summary of the input determination scheme + * <ol> + * <li>Obtain selected mappings + * <li>Project mappings onto resources using the appropriate + * context(s) in order to obtain a set of ResourceTraverals + * <li>Determine what model providers are interested in the targeted resources + * <li>From those model providers, obtain the set of affected resource mappings + * <li>If the original set is the same as the new set, we are done. + * <li>if the set differs from the original selection, rerun the mapping process + * for any new mappings + * <ul> + * <li>Only need to query model providers for mappings for new resources + * <li>If new mappings are obtained, + * ask model provider to compress the mappings? + * <li>keep repeating until no new mappings or resources are added + * </ul> + * <li>Compress the mappings from each provider + * <li>flag overlapping mappings from independent providers + * <li>Display the original set and the new set with an explanation + * <ul> + * <li>The original set and final set may involve mappings from + * multiple providers. + * <li>The number of providers can be reduced by assuming that + * extending models can display the elements of extended models. + * Then we are only left with conflicting models. + * <li>Could use a content provider approach a.k.a. Common Navigator + * or component based approach + * </ul> + * </ol> + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public class ResourceMappingOperationInput extends SimpleResourceMappingOperationInput { + + private final Map inputMappingsToResources = new HashMap(); + private final Map targetMappingsToResources = new HashMap(); + private boolean hasAdditionalMappings; + + public ResourceMappingOperationInput(ResourceMapping[] mappings, ResourceMappingContext context) { + super(mappings, context); + } + + public void buildInput(IProgressMonitor monitor) throws CoreException { + monitor.beginTask(null, IProgressMonitor.UNKNOWN); + buildInputMappingToResourcesMap(Policy.subMonitorFor(monitor, IProgressMonitor.UNKNOWN)); + Set targetMappings = inputMappingsToResources.keySet(); + Set handledResources = new HashSet(); + Set newResources; + do { + newResources = addToTargetMappingToResourceMap(targetMappings, Policy.subMonitorFor(monitor, IProgressMonitor.UNKNOWN)); + IResource[] adjusted = adjustNewResources(newResources); + targetMappings = internalGetMappingsFromProviders(adjusted, getAffectedNatures(targetMappings), Policy.subMonitorFor(monitor, IProgressMonitor.UNKNOWN)); + + //TODO: The new resources aren't really just the new ones so reduce the set if needed + if (!handledResources.isEmpty()) { + for (Iterator iter = newResources.iterator(); iter.hasNext();) { + IResource resource = (IResource) iter.next(); + if (handledResources.contains(resource)) { + iter.remove(); + } + } + } + + handledResources.addAll(newResources); + + } while (!newResources.isEmpty()); + hasAdditionalMappings = internalHasAdditionalMappings(); + } + + /* + * Give the subclass a chance to add resources to the set of affected resources + */ + private IResource[] adjustNewResources(Set newResources) { + IResource[] resources = (IResource[]) newResources.toArray(new IResource[newResources.size()]); + IResource[] adjusted = adjustInputResources(resources); + return adjusted; + } + + /** + * Adjust the given set of input resources to include any additional + * resources required by a particular repository provider for the current + * operation. By default the original set is returned but subclasses may + * override. Overriding methods should return a set of resources that + * include the original resource either explicitly or implicitly as a child + * of a returned resource. + * + * @param resources the input resources + * @return the input resources adjusted to include any additional resources + * required for the current operation + */ + protected IResource[] adjustInputResources(IResource[] resources) { + return resources; + } + + private boolean internalHasAdditionalMappings() { + ResourceMapping[] inputMappings = getSeedMappings(); + if (inputMappings .length == targetMappingsToResources.size()) { + for (int i = 0; i < inputMappings.length; i++) { + ResourceMapping mapping = inputMappings[i]; + if (!targetMappingsToResources.containsKey(mapping)) { + return true; + } + } + return false; + } + return true; + } + + private String[] getAffectedNatures(Set targetMappings) { + Set result = new HashSet(); + for (Iterator iter = targetMappings.iterator(); iter.hasNext();) { + ResourceMapping mapping = (ResourceMapping) iter.next(); + IProject[] projects = mapping.getProjects(); + for (int j = 0; j < projects.length; j++) { + IProject project = projects[j]; + try { + result.addAll(Arrays.asList(project.getDescription().getNatureIds())); + } catch (CoreException e) { + TeamUIPlugin.log(e); + } + } + } + return (String[]) result.toArray(new String[result.size()]); + } + + private Set internalGetMappingsFromProviders(IResource[] resources, String[] affectedNatures, IProgressMonitor monitor) throws CoreException { + Set result = new HashSet(); + IModelProviderDescriptor[] descriptors = ModelProvider.getModelProviderDescriptors(); + for (int i = 0; i < descriptors.length; i++) { + IModelProviderDescriptor descriptor = descriptors[i]; + ResourceMapping[] mappings = getMappings(descriptor, resources, affectedNatures, getContext(), monitor); + result.addAll(Arrays.asList(mappings)); + } + return result; + } + + public ResourceMapping[] getMappings(IModelProviderDescriptor descriptor, IResource[] resources, String[] affectedNatures, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + IResource[] matchingResources = descriptor.getMatchingResources(resources, affectedNatures); + return descriptor.getModelProvider().getMappings(matchingResources, context, monitor); + } + + private Set addToTargetMappingToResourceMap(Set targetMappings, IProgressMonitor monitor) throws CoreException { + Set newResources = new HashSet(); + for (Iterator iter = targetMappings.iterator(); iter.hasNext();) { + ResourceMapping mapping = (ResourceMapping) iter.next(); + if (!targetMappingsToResources.containsKey(mapping)) { + ResourceTraversal[] traversals = mapping.getTraversals(getContext(), Policy.subMonitorFor(monitor, 100)); + targetMappingsToResources.put(mapping, traversals); + newResources.addAll(internalGetResources(traversals)); + } + } + return newResources; + } + + private Collection internalGetResources(ResourceTraversal[] traversals) { + Set result = new HashSet(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + IResource[] resources = traversal.getResources(); + for (int j = 0; j < resources.length; j++) { + IResource resource = resources[j]; + //TODO: should we check for parent/child relationships? + result.add(resource); + } + } + return result; + } + + private void buildInputMappingToResourcesMap(IProgressMonitor monitor) throws CoreException { + ResourceMapping[] inputMappings = getSeedMappings(); + monitor.beginTask(null, inputMappings.length * 100); + for (int i = 0; i < inputMappings.length; i++) { + ResourceMapping mapping = inputMappings[i]; + ResourceTraversal[] traversals = mapping.getTraversals(getContext(), Policy.subMonitorFor(monitor, 100)); + inputMappingsToResources.put(mapping, traversals); + } + monitor.done(); + } + + public ResourceMapping[] getInputMappings() { + return (ResourceMapping[]) targetMappingsToResources.keySet().toArray(new ResourceMapping[targetMappingsToResources.size()]); + } + + public ResourceTraversal[] getInputTraversals() { + Collection values = targetMappingsToResources.values(); + List result = new ArrayList(); + for (Iterator iter = values.iterator(); iter.hasNext();) { + ResourceTraversal[] traversals = (ResourceTraversal[]) iter.next(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + result.add(traversal); + } + } + return combineTraversals((ResourceTraversal[]) result.toArray(new ResourceTraversal[result.size()])); + } + + public ResourceTraversal[] getTraversal(ResourceMapping mapping) { + return (ResourceTraversal[])targetMappingsToResources.get(mapping); + } + + public boolean hasAdditionalMappings() { + return hasAdditionalMappings; + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/SynchronizeOperationContext.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/SynchronizeOperationContext.java new file mode 100644 index 000000000..00577d4e0 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/SynchronizeOperationContext.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.Platform; +import org.eclipse.jface.util.ListenerList; +import org.eclipse.jface.util.SafeRunnable; + +/** + * A synchronize operation context that supports caching of + * properties relevant to the operation and the registering of + * dispose listeners. + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @see org.eclipse.team.ui.mapping.ISynchronizeOperationContext + * @since 3.2 + */ +public abstract class SynchronizeOperationContext extends TeamViewerContext implements ISynchronizeOperationContext { + + Map properties; + ListenerList listeners; + private final IResourceMappingOperationInput input; + + /** + * Create an operation context for the given input. + * @param input the input of the context + */ + public SynchronizeOperationContext(IResourceMappingOperationInput input) { + super(input.getInputMappings()); + this.input = input; + } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.mapping.ISynchronizeOperationContext#addProperty(java.lang.String, java.lang.Object) + */ + public synchronized void addProperty(String name, Object value) { + if (properties == null) { + properties = new HashMap(); + } + properties.put(name, value); + } + + public synchronized Object getProperty(String name) { + if (properties == null) + return null; + return properties.get(name); + } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.mapping.ISynchronizeOperationContext#removeProperty(java.lang.String) + */ + public synchronized void removeProperty(String name) { + if (properties != null) + properties.remove(name); + if (properties.isEmpty()) { + properties = null; + } + + } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.mapping.ISynchronizeOperationContext#addDisposeListener(org.eclipse.team.ui.mapping.IDisposeListener) + */ + public synchronized void addDisposeListener(IDisposeListener listener) { + if (listeners == null) + listeners = new ListenerList(); + listeners.add(listener); + + } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.mapping.ISynchronizeOperationContext#removeDisposeListener(org.eclipse.team.ui.mapping.IDisposeListener) + */ + public synchronized void removeDisposeListener(IDisposeListener listener) { + if (listeners != null) + listeners.remove(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.mapping.ISynchronizationContext#dispose() + */ + public void dispose() { + if (listeners != null) { + Object[] allListeners = listeners.getListeners(); + for (int i = 0; i < allListeners.length; i++) { + final Object listener = allListeners[i]; + Platform.run(new SafeRunnable(){ + public void run() throws Exception { + ((IDisposeListener)listener).contextDisposed(SynchronizeOperationContext.this); + } + }); + } + } + properties = null; + } + + /** + * Return the input used to create this operation context. + * @return the input used to create this operation context + */ + public IResourceMappingOperationInput getInput() { + return input; + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/TeamViewerContext.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/TeamViewerContext.java new file mode 100644 index 000000000..b7be918e8 --- /dev/null +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/mapping/TeamViewerContext.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.ui.mapping; + +import java.util.*; + +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.resources.mapping.ResourceMapping; + +/** + * A concrete implementation of the <code>ITeamViewerContext</code> + * + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added as + * part of a work in progress. There is a guarantee neither that this API will + * work nor that it will remain the same. Please do not use this API without + * consulting with the Platform/Team team. + * </p> + * + * @since 3.2 + */ +public class TeamViewerContext implements ITeamViewerContext { + + private final ResourceMapping[] mappings; + + public TeamViewerContext(ResourceMapping[] mappings) { + this.mappings = mappings; + } + + public ResourceMapping[] getResourceMappings(String modelProviderId) { + if (modelProviderId.equals(ALL_MAPPINGS)) { + return mappings; + } + List result = new ArrayList(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + if (mapping.getModelProviderId().equals(modelProviderId)) { + result.add(mapping); + } + } + return (ResourceMapping[]) result.toArray(new ResourceMapping[result.size()]); + } + + public ModelProvider[] getModelProviders() { + Set providers = new HashSet(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + providers.add(mapping.getModelProvider()); + } + return (ModelProvider[]) providers.toArray(new ModelProvider[providers.size()]); + } + +} diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/synchronize/AbstractSynchronizeScope.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/synchronize/AbstractSynchronizeScope.java index cbc3d0c03..e00bd5398 100644 --- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/synchronize/AbstractSynchronizeScope.java +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/synchronize/AbstractSynchronizeScope.java @@ -10,7 +10,12 @@ *******************************************************************************/ package org.eclipse.team.ui.synchronize; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.ListenerList; @@ -163,4 +168,29 @@ public abstract class AbstractSynchronizeScope implements ISynchronizeScope { protected void init(IMemento memento) { // Do nothing by default } + + /* (non-Javadoc) + * @see org.eclipse.team.ui.synchronize.ISynchronizeScope#contains(org.eclipse.core.resources.IResource) + */ + public boolean contains(IResource resource) { + IResource[] roots = getRoots(); + IPath resourcePath = resource.getFullPath(); + for (int i = 0; i < roots.length; i++) { + IResource root = roots[i]; + if (root.getFullPath().isPrefixOf(resourcePath)) { + return true; + } + } + return false; + } + + public ResourceMapping[] getResourceMappings() { + List result = new ArrayList(); + IResource[] roots = getRoots(); + for (int i = 0; i < roots.length; i++) { + IResource resource = roots[i]; + result.add(resource.getAdapter(ResourceMapping.class)); + } + return (ResourceMapping[]) result.toArray(new ResourceMapping[result.size()]); + } } diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/synchronize/ISynchronizeScope.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/synchronize/ISynchronizeScope.java index 0ade028e1..7a1961388 100644 --- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/synchronize/ISynchronizeScope.java +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/synchronize/ISynchronizeScope.java @@ -11,6 +11,7 @@ package org.eclipse.team.ui.synchronize; import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceMapping; import org.eclipse.jface.util.IPropertyChangeListener; /** @@ -36,6 +37,14 @@ public interface ISynchronizeScope { public static final String NAME = "prop_name"; //$NON-NLS-1$ /** + * Property used to indicate that the roots of the scope have + * not changes but the resources covered by the scope have. + * @see ISynchronizeScope#contains(IResource) + * @since 3.2 + */ + public static final String CONTAINMENT = "prop_containment"; //$NON-NLS-1$ + + /** * Return the name of the scope * * @return the name of the scope @@ -71,4 +80,20 @@ public interface ISynchronizeScope { * Dispose of the scope when it is no longer needed. */ public void dispose(); + + /** + * Return whether the given resource is contained in this scope. + * @param resource the resource to be tested + * @return whether the given resource is contained in this scope + * + * @since 3.2 + */ + public boolean contains(IResource resource); + + /** + * + * @return + * @since 3.2 + */ + public ResourceMapping[] getResourceMappings(); } diff --git a/tests/org.eclipse.team.tests.cvs.core/launchConfigurations/CVS UI Tests.launch b/tests/org.eclipse.team.tests.cvs.core/launchConfigurations/CVS UI Tests.launch index a91334527..67cb9dc59 100644 --- a/tests/org.eclipse.team.tests.cvs.core/launchConfigurations/CVS UI Tests.launch +++ b/tests/org.eclipse.team.tests.cvs.core/launchConfigurations/CVS UI Tests.launch @@ -55,19 +55,19 @@ <mapEntry key="org.eclipse.help.ui/debug/ieadapter/inprocess" value="false"/> <mapEntry key="org.eclipse.jface/trace/actions" value="false"/> <mapEntry key="org.eclipse.core.runtime/registry/debug/events/extension" value="false"/> -<mapEntry key="org.eclipse.help.webapp/debug" value="true"/> <mapEntry key="org.eclipse.jdt.core/debug/hierarchy" value="false"/> +<mapEntry key="org.eclipse.help.webapp/debug" value="true"/> <mapEntry key="org.eclipse.team.cvs.core/debug" value="true"/> <mapEntry key="org.eclipse.jdt.core/debug/cpresolution" value="false"/> <mapEntry key="org.eclipse.team.cvs.core/threading" value="false"/> <mapEntry key="org.eclipse.update.core/debug/warning" value="false"/> <mapEntry key="org.eclipse.core.resources/save" value="false"/> -<mapEntry key="org.eclipse.ui/debug/internalerror/openDialog" value="false"/> <mapEntry key="org.eclipse.update.core/debug/install" value="false"/> +<mapEntry key="org.eclipse.ui/debug/internalerror/openDialog" value="false"/> <mapEntry key="org.eclipse.core.resources/debug" value="false"/> <mapEntry key="org.eclipse.update.core/debug/parsing" value="false"/> -<mapEntry key="org.eclipse.core.boot/trace/filters" value="trace.properties"/> <mapEntry key="org.eclipse.core.resources/restore/syncinfo" value="false"/> +<mapEntry key="org.eclipse.core.boot/trace/filters" value="trace.properties"/> <mapEntry key="org.eclipse.team.cvs.core/cvsprotocol" value="true"/> <mapEntry key="org.eclipse.jdt.core/debug/postaction" value="false"/> <mapEntry key="org.eclipse.core.runtime/url/debug/cachelookup" value="false"/> @@ -77,15 +77,15 @@ <mapEntry key="org.eclipse.core.resources/save/syncinfo" value="false"/> <mapEntry key="org.eclipse.core.runtime/loader/debug/filter/resource" value="*"/> <mapEntry key="org.eclipse.team.ftp/list" value="true"/> -<mapEntry key="org.eclipse.team.cvs.ssh/debug" value="false"/> <mapEntry key="org.eclipse.core.runtime/loader/debug/activateplugin" value="false"/> +<mapEntry key="org.eclipse.team.cvs.ssh/debug" value="false"/> <mapEntry key="org.eclipse.core.resources/monitor/builders" value="false"/> <mapEntry key="org.eclipse.core.resources/save/markers" value="false"/> <mapEntry key="org.eclipse.update.core/debug/installhandler" value="false"/> -<mapEntry key="org.eclipse.jdt.core/debug/selection" value="false"/> <mapEntry key="org.eclipse.jdt.core/debug/javadelta" value="false"/> -<mapEntry key="org.eclipse.ui/trace/workbench.restore" value="false"/> +<mapEntry key="org.eclipse.jdt.core/debug/selection" value="false"/> <mapEntry key="org.eclipse.core.resources/restore/tree" value="false"/> +<mapEntry key="org.eclipse.ui/trace/workbench.restore" value="false"/> <mapEntry key="org.eclipse.ui/trace/part.create" value="false"/> <mapEntry key="org.eclipse.help/debug/protocols" value="false"/> <mapEntry key="org.eclipse.core.resources/restore/snapshots" value="false"/> @@ -93,13 +93,13 @@ <mapEntry key="org.eclipse.team.cvs.core/dirtycaching" value="false"/> <mapEntry key="org.eclipse.core.runtime/loader/debug/filter/native" value="*"/> <mapEntry key="org.eclipse.jdt.core/debug/zipaccess" value="false"/> -<mapEntry key="org.eclipse.core.runtime/loader/debug/properties" value="false"/> <mapEntry key="org.eclipse.core.runtime/url/debug/cachecopy" value="false"/> +<mapEntry key="org.eclipse.core.runtime/loader/debug/properties" value="false"/> <mapEntry key="org.eclipse.core.runtime/loader/debug/create" value="false"/> <mapEntry key="org.eclipse.core.runtime/jobs/beginend" value="false"/> <mapEntry key="org.eclipse.team.cvs.core/metafiles" value="false"/> -<mapEntry key="org.eclipse.team.core/debug" value="false"/> <mapEntry key="org.eclipse.team.ftp/responses" value="true"/> +<mapEntry key="org.eclipse.team.core/debug" value="false"/> <mapEntry key="org.eclipse.core.runtime/loader/debug/prefixes/success" value="false"/> <mapEntry key="org.eclipse.core.runtime/jobs" value="false"/> <mapEntry key="org.eclipse.core.boot/monitor/plugins" value="false"/> @@ -125,8 +125,8 @@ <mapEntry key="org.eclipse.core.runtime/config/debug" value="false"/> <mapEntry key="org.eclipse.help.ui/debug" value="true"/> <mapEntry key="org.eclipse.core.resources/restore/metainfo" value="false"/> -<mapEntry key="org.eclipse.debug.ui/debug" value="true"/> <mapEntry key="org.eclipse.core.resources/save/mastertable" value="false"/> +<mapEntry key="org.eclipse.debug.ui/debug" value="true"/> <mapEntry key="org.eclipse.jdt.debug/debug" value="true"/> <mapEntry key="org.eclipse.core.runtime/debug" value="false"/> <mapEntry key="org.eclipse.jdt.core/debug/buffermanager" value="false"/> @@ -142,17 +142,18 @@ <booleanAttribute key="automaticAdd" value="true"/> <stringAttribute key="checked" value="[NONE]"/> <booleanAttribute key="includeFragments" value="false"/> +<stringAttribute key="location" value="C:\Eclipse\Latest-Eclipse-Drop\eclipse\runtime-test-workspace"/> <booleanAttribute key="clearws" value="true"/> <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.team.tests.ccvs.core.AllTests"/> <stringAttribute key="location1" value="C:\Eclipse\Latest-Eclipse-Drop\eclipse\runtime-test-workspace"/> -<stringAttribute key="vmargs" value="-Declipse.cvs.properties=c:\eclipse\repository.properties -Declipse.cvs.testName2=testFileAdditions"/> +<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os win32 -ws win32 -arch x86 -nl en_CA"/> +<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Declipse.cvs.properties=c:\eclipse\repository.properties -Declipse.cvs.testName2=testFileAdditions"/> <booleanAttribute key="default" value="true"/> <booleanAttribute key="clearConfig" value="true"/> <booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/> -<booleanAttribute key="useDefaultConfigArea" value="true"/> <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/> +<booleanAttribute key="useDefaultConfigArea" value="true"/> <stringAttribute key="onePluginID" value=""/> -<stringAttribute key="progargs" value="-os win32 -ws win32 -arch x86 -nl en_CA"/> <booleanAttribute key="useDefaultConfig" value="true"/> <stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.team.tests.cvs.core"/> @@ -160,5 +161,4 @@ <booleanAttribute key="onePlugin" value="false"/> <booleanAttribute key="includeOptional" value="true"/> <booleanAttribute key="maximized" value="false"/> -<stringAttribute key="location0" value="C:\Eclipse\Latest-Eclipse-Drop\eclipse\runtime-test-workspace"/> </launchConfiguration> diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/EclipseTest.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/EclipseTest.java index 3df328a32..1c9a3927d 100644 --- a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/EclipseTest.java +++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/EclipseTest.java @@ -833,15 +833,19 @@ public class EclipseTest extends ResourceTest { } protected void setContentsAndEnsureModified(IFile file) throws CoreException, TeamException { - setContentsAndEnsureModified(file, getRandomContents().toString()); + setContentsAndEnsureModified(file, getRandomContents()); } protected void setContentsAndEnsureModified(IFile file, String contents) throws CoreException, CVSException { + if (contents == null) contents =""; + setContentsAndEnsureModified(file, new ByteArrayInputStream(contents.getBytes())); + } + + protected void setContentsAndEnsureModified(IFile file, InputStream stream) throws CoreException, CVSException { ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor(file); int count = 0; - if (contents == null) contents =""; do { - file.setContents(new ByteArrayInputStream(contents.getBytes()), false, false, null); + file.setContents(stream, false, false, null); assertTrue("Timestamp granularity is too small. Increase test wait factor", count <= CVSTestSetup.WAIT_FACTOR); if (!cvsFile.isModified(null)) { waitMsec(1500); diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/mappings/ResourceMapperTests.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/mappings/ResourceMapperTests.java index f28c6da4e..ff7fd6fa9 100644 --- a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/mappings/ResourceMapperTests.java +++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/mappings/ResourceMapperTests.java @@ -11,21 +11,30 @@ package org.eclipse.team.tests.ccvs.core.mappings; import java.io.IOException; +import java.io.InputStream; import java.util.*; import junit.framework.Test; +import org.eclipse.core.internal.resources.mapping.SimpleResourceMapping; import org.eclipse.core.resources.*; import org.eclipse.core.resources.mapping.*; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.osgi.util.NLS; import org.eclipse.team.core.TeamException; import org.eclipse.team.core.synchronize.*; +import org.eclipse.team.core.variants.CachedResourceVariant; +import org.eclipse.team.core.variants.IResourceVariant; import org.eclipse.team.internal.ccvs.core.*; import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption; import org.eclipse.team.internal.ccvs.core.resources.RemoteFolderTree; import org.eclipse.team.internal.ccvs.core.resources.RemoteFolderTreeBuilder; import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo; +import org.eclipse.team.internal.ccvs.ui.operations.CacheBaseContentsOperation; +import org.eclipse.team.internal.ccvs.ui.operations.CacheRemoteContentsOperation; +import org.eclipse.team.internal.core.ResourceVariantCache; +import org.eclipse.team.internal.core.ResourceVariantCacheEntry; import org.eclipse.team.tests.ccvs.core.EclipseTest; /** @@ -276,6 +285,9 @@ public class ResourceMapperTests extends EclipseTest { new ResourceTraversal(resources, depth, IResource.NONE) }; } + public String getModelProviderId() { + return ModelProvider.RESOURCE_MODEL_PROVIDER_ID; + } }; } @@ -531,4 +543,112 @@ public class ResourceMapperTests extends EclipseTest { add(asResourceMapping(new IResource[] { project }, IResource.DEPTH_INFINITE)); } + public void testCacheBase() throws TeamException, CoreException { + IProject project = createProject("testCacheBase", new String[] { "changed.txt", "deleted.txt", "folder1/", "folder1/a.txt", "folder1/b.txt", "folder1/subfolder1/c.txt" }); + IProject copy = checkoutCopy(project, "-copy"); + + // First, make some local changes and then cache the bases + setContentsAndEnsureModified(project.getFile("changed.txt"), "Uncomited text"); + setContentsAndEnsureModified(project.getFile("folder1/b.txt")); + project.getFile("deleted.txt").delete(false, true, null); + cacheBase(project, true /* cache for outgoing and conflicting */); + cacheBase(project, false /* cache for conflicting only*/); + + // Next, retry after releasing some changes (to ensure proper contents are fetched) + setContentsAndEnsureModified(copy.getFile("changed.txt"), "Text comited from the copy"); + commitProject(copy); + cacheBase(project, true /* cache for outgoing and conflicting */); + cacheBase(project, false /* cache for conflicting only */); + } + + public void testCacheRemote() throws TeamException, CoreException { + IProject project = createProject("testCacheRemote", new String[] { "changed.txt", "deleted.txt", "folder1/", "folder1/a.txt", "folder1/b.txt", "folder1/subfolder1/c.txt" }); + IProject copy = checkoutCopy(project, "-copy"); + + // Make some remote changes + setContentsAndEnsureModified(copy.getFile("changed.txt"), "Uncomited text"); + setContentsAndEnsureModified(copy.getFile("folder1/b.txt")); + commitProject(copy); + // Delete a local file + project.getFile("deleted.txt").delete(false, true, null); + cacheRemote(project); + } + + private void cacheRemote(IProject project) throws TeamException { + clearCache(project); + CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber().refresh(new IProject[] { project }, IResource.DEPTH_INFINITE, DEFAULT_MONITOR); + SyncInfoTree tree = getAllOutOfSync(new IProject[] { project }); + ResourceMapping[] mappings = new ResourceMapping[] {new SimpleResourceMapping(project)}; + CacheRemoteContentsOperation op = new CacheRemoteContentsOperation(null, mappings, tree); + executeHeadless(op); + ensureRemoteCached(tree); + } + + private void cacheBase(IProject project, boolean includeOutgoing) throws CoreException { + clearCache(project); + CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber().refresh(new IProject[] { project }, IResource.DEPTH_INFINITE, DEFAULT_MONITOR); + SyncInfoTree tree = getAllOutOfSync(new IProject[] { project }); + ResourceMapping[] mappings = new ResourceMapping[] {new SimpleResourceMapping(project)}; + CacheBaseContentsOperation op = new CacheBaseContentsOperation(null, mappings, tree, includeOutgoing); + executeHeadless(op); + ensureBaseCached(tree, includeOutgoing); + } + + private void ensureRemoteCached(SyncInfoTree tree) { + for (Iterator iter = tree.iterator(); iter.hasNext();) { + SyncInfo info = (SyncInfo) iter.next(); + IResourceVariant remote = info.getRemote(); + if (remote != null) { + boolean isCached = ((CachedResourceVariant)remote).isContentsCached(); + int direction = SyncInfo.getDirection(info.getKind()); + if (direction == SyncInfo.CONFLICTING || direction == SyncInfo.INCOMING) { + assertTrue(NLS.bind("The remote contents should be cached for {0}", new String[] {info.getLocal().getFullPath().toString()}), isCached); + } else { + assertFalse(NLS.bind("The base contents should NOT be cached for {0}", new String[] {info.getLocal().getFullPath().toString()}), isCached); + } + } + } + } + + private void ensureBaseCached(SyncInfoTree tree, boolean includeOutgoing) throws TeamException, CoreException { + for (Iterator iter = tree.iterator(); iter.hasNext();) { + SyncInfo info = (SyncInfo) iter.next(); + IResourceVariant base = info.getBase(); + if (base != null) { + boolean isCached = ((CachedResourceVariant)base).isContentsCached(); + int direction = SyncInfo.getDirection(info.getKind()); + if (direction == SyncInfo.CONFLICTING || (includeOutgoing && direction == SyncInfo.OUTGOING)) { + assertTrue(NLS.bind("The base contents should be cached for {0}", new String[] {info.getLocal().getFullPath().toString()}), isCached); + // For conflicts, ensure that the cache contents do not match the remote + if (direction == SyncInfo.CONFLICTING) { + IResourceVariant remote = info.getRemote(); + if (remote != null) { + InputStream baseIn = base.getStorage(DEFAULT_MONITOR).getContents(); + if (baseIn == null) { + fail(NLS.bind("Base was not fetched for for {0}", new String[] {info.getLocal().getFullPath().toString()})); + } + InputStream remoteIn = remote.getStorage(DEFAULT_MONITOR).getContents(); + if (compareContent(baseIn, remoteIn)) { + fail(NLS.bind("The remote was fetched instead of the base for {0}", new String[] {info.getLocal().getFullPath().toString()})); + } + } + } + } else { + assertFalse(NLS.bind("The base contents should NOT be cached for {0}", new String[] {info.getLocal().getFullPath().toString()}), isCached); + } + } + } + } + + private void clearCache(IProject project) { + ResourceVariantCache cache = ResourceVariantCache.getCache(CVSProviderPlugin.ID); + if (cache != null) { + ResourceVariantCacheEntry[] entries = cache.getEntries(); + for (int i = 0; i < entries.length; i++) { + ResourceVariantCacheEntry entry = entries[i]; + entry.dispose(); + } + } + } + } diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/mappings/SyncInfoSetTraveralContext.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/mappings/SyncInfoSetTraveralContext.java index ad14b21ed..9eec8540c 100644 --- a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/mappings/SyncInfoSetTraveralContext.java +++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/mappings/SyncInfoSetTraveralContext.java @@ -48,11 +48,10 @@ public class SyncInfoSetTraveralContext extends RemoteResourceMappingContext { /* (non-Javadoc) * @see org.eclipse.core.resources.mapping.ITraversalContext#fetchContents(org.eclipse.core.resources.IFile, org.eclipse.core.runtime.IProgressMonitor) */ - public IStorage fetchContents(IFile file, IProgressMonitor monitor) throws CoreException { + public IStorage fetchRemoteContents(IFile file, IProgressMonitor monitor) throws CoreException { SyncInfo info = getSyncInfo(file); - //TODO: Speced to throw an exception when remote doesn't exist if (info == null) - return file; + return null; IResourceVariant remote = info.getRemote(); if (remote == null) return null; @@ -73,4 +72,35 @@ public class SyncInfoSetTraveralContext extends RemoteResourceMappingContext { // Do nothing } + public boolean isThreeWay() { + for (Iterator iter = set.iterator(); iter.hasNext();) { + SyncInfo info = (SyncInfo) iter.next(); + return info.getComparator().isThreeWay(); + } + return true; + } + + public boolean hasRemoteChange(IResource resource, IProgressMonitor monitor) throws CoreException { + SyncInfo info = set.getSyncInfo(resource); + int direction = SyncInfo.getDirection(info.getKind()); + return direction == SyncInfo.INCOMING || direction == SyncInfo.CONFLICTING; + } + + public boolean hasLocalChange(IResource resource, IProgressMonitor monitor) throws CoreException { + SyncInfo info = set.getSyncInfo(resource); + int direction = SyncInfo.getDirection(info.getKind()); + return direction == SyncInfo.OUTGOING || direction == SyncInfo.CONFLICTING; + + } + + public IStorage fetchBaseContents(IFile file, IProgressMonitor monitor) throws CoreException { + SyncInfo info = getSyncInfo(file); + if (info == null) + return null; + IResourceVariant base = info.getBase(); + if (base == null) + return null; + return base.getStorage(monitor); + } + } |