42566: ConcurrentModificationException during Team refresh with all repositories
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 440752c..4af5c1c 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
@@ -80,7 +80,7 @@
 		// TODO: hack for clearing the remote state when anything to the resource
 		// sync is changed. Should be able to set the *right* remote/base based on
 		// the sync being set.
-		// TODO: This will throw exceptions if performed during the POST_CHANGE delta phase!!!
+		// IMPORTANT NOTE: This will throw exceptions if performed during the POST_CHANGE delta phase!!!
 		for (int i = 0; i < changedResources.length; i++) {
 			IResource resource = changedResources[i];
 			try {
@@ -107,7 +107,7 @@
 	 * @see org.eclipse.team.internal.ccvs.core.IResourceStateChangeListener#resourceModified(org.eclipse.core.resources.IResource[])
 	 */
 	public void resourceModified(IResource[] changedResources) {
-		// TODO: This is only ever called from a delta POST_CHANGE
+		// This is only ever called from a delta POST_CHANGE
 		// which causes problems since the workspace tree is closed
 		// for modification and we flush the sync info in resourceSyncInfoChanged
 		
@@ -158,7 +158,6 @@
 		final List result = new ArrayList();
 		for (int i = 0; i < resources.length; i++) {
 			IResource resource = resources[i];
-			ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
 			final IProgressMonitor infinite = Policy.infiniteSubMonitorFor(monitor, 100);
 			try {
 				// We need to do a scheduling rule on the project because
@@ -167,17 +166,17 @@
 				Platform.getJobManager().beginRule(resource);
 				infinite.beginTask(null, 512);
 				resource.accept(new IResourceVisitor() {
-					public boolean visit(IResource resource) throws CoreException {
+					public boolean visit(IResource innerResource) throws CoreException {
 						try {
-							if (isOutOfSync(resource, infinite)) {
-								SyncInfo info = getSyncInfo(resource, infinite);
+							if (isOutOfSync(innerResource, infinite)) {
+								SyncInfo info = getSyncInfo(innerResource, infinite);
 								if (info != null && info.getKind() != 0) {
 									result.add(info);
 								}
 							}
 							return true;
 						} catch (TeamException e) {
-							// TODO: This is probably not the right thing to do here
+							// TODO:See bug 42795
 							throw new CoreException(e.getStatus());
 						}
 					}
@@ -193,7 +192,7 @@
 		return (SyncInfo[]) result.toArray(new SyncInfo[result.size()]);
 	}
 	
-	private boolean isOutOfSync(IResource resource, IProgressMonitor monitor) throws CVSException {
+	/* internal use only */ boolean isOutOfSync(IResource resource, IProgressMonitor monitor) throws CVSException {
 		return (hasIncomingChange(resource) || hasOutgoingChange(CVSWorkspaceRoot.getCVSResourceFor(resource), monitor));
 	}
 	
@@ -201,18 +200,17 @@
 		if (resource.isFolder()) {
 			// A folder is an outgoing change if it is not a CVS folder and not ignored
 			ICVSFolder folder = (ICVSFolder)resource;
-			// TODO: The parent caches the dirty state so we only need to check
-			// the file if the parent is dirty.
-			// TODO: Unfortunately, the modified check on the parent still loads
-			// the CVS folder information so not much is gained
+			// OPTIMIZE: The following checks load the CVS folder information
 			if (folder.getParent().isModified(monitor)) {
 				return !folder.isCVSFolder() && !folder.isIgnored();
 			}
 		} else {
 			// A file is an outgoing change if it is modified
 			ICVSFile file = (ICVSFile)resource;
-			// TODO: The parent chaches the dirty state so we only need to check
+			// The parent caches the dirty state so we only need to check
 			// the file if the parent is dirty
+			// OPTIMIZE: Unfortunately, the modified check on the parent still loads
+			// the CVS folder information so not much is gained
 			if (file.getParent().isModified(monitor)) {
 				return file.isModified(monitor);
 			}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/messages.properties b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/messages.properties
index 3e08ef5..66a5517 100644
--- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/messages.properties
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/messages.properties
@@ -328,3 +328,4 @@
 CVSSyncTreeSubscriber.0={0} is not a valid comparison criteria for subscriber {1}
 CVSRevisionNumberCompareCriteria.1=Revision number comparison
 RemoteTagSynchronizer.0=Refreshing {0}
+ReentrantLock.9=An error occurred writting CVS synchronization information to disk. Some information may be lost.
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseSynchronizer.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseSynchronizer.java
index a449e14..44fc513 100644
--- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseSynchronizer.java
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseSynchronizer.java
@@ -28,6 +28,7 @@
 import org.eclipse.core.resources.IResourceStatus;
 import org.eclipse.core.resources.IResourceVisitor;
 import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.IWorkspaceRunnable;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
@@ -37,7 +38,6 @@
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.jobs.ILock;
-import org.eclipse.core.runtime.jobs.ISchedulingRule;
 import org.eclipse.team.internal.ccvs.core.CVSException;
 import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
 import org.eclipse.team.internal.ccvs.core.CVSStatus;
@@ -49,7 +49,10 @@
 import org.eclipse.team.internal.ccvs.core.syncinfo.BaserevInfo;
 import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
 import org.eclipse.team.internal.ccvs.core.syncinfo.NotifyInfo;
+import org.eclipse.team.internal.ccvs.core.syncinfo.ReentrantLock;
 import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
+import org.eclipse.team.internal.ccvs.core.syncinfo.ReentrantLock.IFlushOperation;
+import org.eclipse.team.internal.ccvs.core.syncinfo.ReentrantLock.ThreadInfo;
 import org.eclipse.team.internal.ccvs.core.util.Assert;
 import org.eclipse.team.internal.ccvs.core.util.FileNameMatcher;
 import org.eclipse.team.internal.ccvs.core.util.SyncFileWriter;
@@ -67,10 +70,17 @@
  * Special processing has been added for linked folders and their childen so
  * that their CVS meta files are never read or written.
  * 
+ * IMPORTANT NOTICE: It is the reponsibility of the clients of EclipseSynchronizer
+ * to ensure that they have wrapped operations that may modify the workspace in
+ * an IWorkspaceRunnable. If this is not done, deltas may fore at inopertune times 
+ * and corrupt the sync info. The wrapping could be done within the synchronizer 
+ * itself but would require the creation of an inner class for each case that requires
+ * it.
+ * 
  * @see ResourceSyncInfo
  * @see FolderSyncInfo
  */
-public class EclipseSynchronizer {	
+public class EclipseSynchronizer implements IFlushOperation {	
 	private static final String IS_DIRTY_INDICATOR = SyncInfoCache.IS_DIRTY_INDICATOR;
 	private static final String NOT_DIRTY_INDICATOR = SyncInfoCache.NOT_DIRTY_INDICATOR;
 	private static final String RECOMPUTE_INDICATOR = SyncInfoCache.RECOMPUTE_INDICATOR; 
@@ -80,9 +90,7 @@
 	
 	// track resources that have changed in a given operation
 	private ILock lock = Platform.getJobManager().newLock();
-	
-	private Set changedResources = new HashSet();
-	private Set changedFolders = new HashSet();
+	private ReentrantLock resourceLock = new ReentrantLock();
 	
 	private SessionPropertySyncInfoCache sessionPropertyCache = new SessionPropertySyncInfoCache();
 	private SynchronizerSyncInfoCache synchronizerCache = new SynchronizerSyncInfoCache();
@@ -132,18 +140,23 @@
 				Policy.bind("EclipseSynchronizer.ErrorSettingFolderSync", folder.getFullPath().toString())); //$NON-NLS-1$
 		}
 		try {
-			beginOperation(folder, null);
-			// get the old info
-			FolderSyncInfo oldInfo = getFolderSync(folder);
-			// set folder sync and notify
-			getSyncInfoCacheFor(folder).setCachedFolderSync(folder, info);
-			// if the sync info changed from null, we may need to adjust the ancestors
-			if (oldInfo == null) {
-				adjustDirtyStateRecursively(folder, RECOMPUTE_INDICATOR);
+			beginBatching(folder);
+			try {
+				beginOperation();
+				// get the old info
+				FolderSyncInfo oldInfo = getFolderSync(folder);
+				// set folder sync and notify
+				getSyncInfoCacheFor(folder).setCachedFolderSync(folder, info);
+				// if the sync info changed from null, we may need to adjust the ancestors
+				if (oldInfo == null) {
+					adjustDirtyStateRecursively(folder, RECOMPUTE_INDICATOR);
+				}
+				folderChanged(folder);
+			} finally {
+				endOperation();
 			}
-			changedFolders.add(folder);
 		} finally {
-			endOperation(null);
+			endBatching(null);
 		}
 	}
 	
@@ -157,11 +170,11 @@
 	public FolderSyncInfo getFolderSync(IContainer folder) throws CVSException {
 		if (folder.getType() == IResource.ROOT || !isValid(folder)) return null;
 		try {
-			beginOperation(folder, null);
+			beginOperation();
 			cacheFolderSync(folder);
 			return getSyncInfoCacheFor(folder).getCachedFolderSync(folder);
 		} finally {
-			endOperation(null);
+			endOperation();
 		}
 	}	
 
@@ -175,27 +188,40 @@
 	public void deleteFolderSync(IContainer folder) throws CVSException {
 		if (folder.getType() == IResource.ROOT || !isValid(folder)) return;
 		try {
-			beginOperation(folder, null);
-			// iterate over all children with sync info and prepare notifications
-			// this is done first since deleting the folder sync may remove a phantom
-			cacheResourceSyncForChildren(folder);
-			IResource[] children = folder.members(true);
-			for (int i = 0; i < children.length; i++) {
-				IResource resource = children[i];
-				changedResources.add(resource);
-				// delete resource sync for all children
-				getSyncInfoCacheFor(resource).setCachedSyncBytes(resource, null);
+			beginBatching(folder);
+			try {
+				beginOperation();
+				// iterate over all children with sync info and prepare notifications
+				// this is done first since deleting the folder sync may remove a phantom
+				cacheResourceSyncForChildren(folder);
+				IResource[] children = folder.members(true);
+				for (int i = 0; i < children.length; i++) {
+					IResource resource = children[i];
+					resourceChanged(resource);
+					// delete resource sync for all children
+					getSyncInfoCacheFor(resource).setCachedSyncBytes(resource, null);
+				}
+				// delete folder sync
+				getSyncInfoCacheFor(folder).setCachedFolderSync(folder, null);
+				folderChanged(folder);
+			} catch (CoreException e) {
+				throw CVSException.wrapException(e);
+			} finally {
+				endOperation();
 			}
-			// delete folder sync
-			getSyncInfoCacheFor(folder).setCachedFolderSync(folder, null);
-			changedFolders.add(folder);
-		} catch (CoreException e) {
-			throw CVSException.wrapException(e);
 		} finally {
-			endOperation(null);
+			endBatching(null);
 		}
 	}
 
+	private void folderChanged(IContainer folder) {
+		resourceLock.folderChanged(folder);
+	}
+
+	private void resourceChanged(IResource resource) {
+		resourceLock.resourceChanged(resource);
+	}
+
 	/**
 	 * Sets the resource sync info for the specified resource.
 	 * The parent folder must exist and must not be the workspace root.
@@ -212,13 +238,18 @@
 				Policy.bind("EclipseSynchronizer.ErrorSettingResourceSync", resource.getFullPath().toString())); //$NON-NLS-1$
 		}
 		try {
-			beginOperation(resource, null);
-			// cache resource sync for siblings, set for self, then notify
-			cacheResourceSyncForChildren(parent);
-			setCachedResourceSync(resource, info);
-			changedResources.add(resource);		
+			beginBatching(resource);
+			try {
+				beginOperation();
+				// cache resource sync for siblings, set for self, then notify
+				cacheResourceSyncForChildren(parent);
+				setCachedResourceSync(resource, info);
+				resourceChanged(resource);		
+			} finally {
+				endOperation();
+			}
 		} finally {
-			endOperation(null);
+			endBatching(null);
 		}
 	}
 	
@@ -246,7 +277,7 @@
 		IContainer parent = resource.getParent();
 		if (parent == null || parent.getType() == IResource.ROOT || !isValid(parent)) return null;
 		try {
-			beginOperation(resource, null);
+			beginOperation();
 			// cache resource sync for siblings, then return for self
 			try {
 				cacheResourceSyncForChildren(parent);
@@ -261,7 +292,7 @@
 			}
 			return getCachedSyncBytes(resource);
 		} finally {
-			endOperation(null);
+			endOperation();
 		}
 	}
 
@@ -281,13 +312,18 @@
 				Policy.bind("EclipseSynchronizer.ErrorSettingResourceSync", resource.getFullPath().toString())); //$NON-NLS-1$
 		}
 		try {
-			beginOperation(resource, null);
-			// cache resource sync for siblings, set for self, then notify
-			cacheResourceSyncForChildren(parent);
-			setCachedSyncBytes(resource, syncBytes);
-			changedResources.add(resource);		
+			beginBatching(resource);
+			try {
+				beginOperation();
+				// cache resource sync for siblings, set for self, then notify
+				cacheResourceSyncForChildren(parent);
+				setCachedSyncBytes(resource, syncBytes);
+				resourceChanged(resource);		
+			} finally {
+				endOperation();
+			}
 		} finally {
-			endOperation(null);
+			endBatching(null);
 		}
 	}
 		
@@ -301,16 +337,21 @@
 		IContainer parent = resource.getParent();
 		if (parent == null || parent.getType() == IResource.ROOT || !isValid(parent)) return;
 		try {
-			beginOperation(resource, null);
-			// cache resource sync for siblings, delete for self, then notify
-			cacheResourceSyncForChildren(parent);
-			if (getCachedSyncBytes(resource) != null) { // avoid redundant notifications
-				setCachedSyncBytes(resource, null);
-				clearDirtyIndicator(resource);
-				changedResources.add(resource);
+			beginBatching(resource);
+			try {
+				beginOperation();
+				// cache resource sync for siblings, delete for self, then notify
+				cacheResourceSyncForChildren(parent);
+				if (getCachedSyncBytes(resource) != null) { // avoid redundant notifications
+					setCachedSyncBytes(resource, null);
+					clearDirtyIndicator(resource);
+					resourceChanged(resource);
+				}
+			} finally {
+				endOperation();
 			}
 		} finally {
-			endOperation(null);
+			endBatching(null);
 		}
 	}
 
@@ -336,11 +377,11 @@
 			return false;
 		} 
 		try {
-			beginOperation(resource, null);
+			beginOperation();
 			FileNameMatcher matcher = cacheFolderIgnores(resource.getParent());
 			return matcher.match(resource.getName());
 		} finally {
-			endOperation(null);
+			endOperation();
 		}
 	}
 	
@@ -356,29 +397,34 @@
 				Policy.bind("EclipseSynchronizer.ErrorSettingIgnorePattern", folder.getFullPath().toString())); //$NON-NLS-1$
 		}
 		try {
-			beginOperation(folder, null);
-			String[] ignores = SyncFileWriter.readCVSIgnoreEntries(folder);
-			if (ignores != null) {
-				// verify that the pattern has not already been added
-				for (int i = 0; i < ignores.length; i++) {
-					if (ignores[i].equals(pattern)) return;
+			beginBatching(folder);
+			try {
+				beginOperation();
+				String[] ignores = SyncFileWriter.readCVSIgnoreEntries(folder);
+				if (ignores != null) {
+					// verify that the pattern has not already been added
+					for (int i = 0; i < ignores.length; i++) {
+						if (ignores[i].equals(pattern)) return;
+					}
+					// add the pattern
+					String[] oldIgnores = ignores;
+					ignores = new String[oldIgnores.length + 1];
+					System.arraycopy(oldIgnores, 0, ignores, 0, oldIgnores.length);
+					ignores[oldIgnores.length] = pattern;
+				} else {
+					ignores = new String[] { pattern };
 				}
-				// add the pattern
-				String[] oldIgnores = ignores;
-				ignores = new String[oldIgnores.length + 1];
-				System.arraycopy(oldIgnores, 0, ignores, 0, oldIgnores.length);
-				ignores[oldIgnores.length] = pattern;
-			} else {
-				ignores = new String[] { pattern };
+				setCachedFolderIgnores(folder, ignores);
+				SyncFileWriter.writeCVSIgnoreEntries(folder, ignores);
+				// broadcast changes to unmanaged children - they are the only candidates for being ignored
+				List possibleIgnores = new ArrayList();
+				accumulateNonManagedChildren(folder, possibleIgnores);
+				CVSProviderPlugin.broadcastSyncInfoChanges((IResource[])possibleIgnores.toArray(new IResource[possibleIgnores.size()]));
+			} finally {
+				endOperation();
 			}
-			setCachedFolderIgnores(folder, ignores);
-			SyncFileWriter.writeCVSIgnoreEntries(folder, ignores);
-			// broadcast changes to unmanaged children - they are the only candidates for being ignored
-			List possibleIgnores = new ArrayList();
-			accumulateNonManagedChildren(folder, possibleIgnores);
-			CVSProviderPlugin.broadcastSyncInfoChanges((IResource[])possibleIgnores.toArray(new IResource[possibleIgnores.size()]));
 		} finally {
-			endOperation(null);
+			endBatching(null);
 		}
 	}
 	
@@ -392,7 +438,7 @@
 	public IResource[] members(IContainer folder) throws CVSException {
 		if (! isValid(folder)) return new IResource[0];
 		try {				
-			beginOperation(folder, null);
+			beginOperation();
 			if (folder.getType() != IResource.ROOT) {
 				// ensure that the sync info is cached so any required phantoms are created
 				cacheResourceSyncForChildren(folder);
@@ -401,35 +447,23 @@
 		} catch (CoreException e) {
 			throw CVSException.wrapException(e);
 		} finally {
-			endOperation(null);
+			endOperation();
 		}
 	}
 	
+	
 	/**
 	 * Begins a batch of operations.
 	 * 
 	 * @param monitor the progress monitor, may be null
 	 */
-	public void beginOperation(IResource resource, IProgressMonitor monitor) throws CVSException {
-		// ensure locks are acquired in the same order: workspace then cvs
-		// The scheduling rule is either the project or the resource's parent
-		ISchedulingRule rule;
-		if (resource.getType() == IResource.PROJECT || resource.getType() == IResource.ROOT) {
-			rule = resource;
-		} else {
-			rule = resource.getParent();
-		}
-		Platform.getJobManager().beginRule(rule);					
-		lock.acquire();
-
-		if (lock.getDepth() == 1) {
-			prepareCache(monitor);
-		}		
+	public void beginBatching(IResource resource) {
+		resourceLock.acquire(resource, this /* IFlushOperation */);
 	}
 	
 	/**
 	 * Ends a batch of operations.  Pending changes are committed only when
-	 * the number of calls to endOperation() balances those to beginOperation().
+	 * the number of calls to endBatching() balances those to beginBatching().
 	 * <p>
 	 * Progress cancellation is ignored while writting the cache to disk. This
 	 * is to ensure cache to disk consistency.
@@ -439,77 +473,103 @@
 	 * @exception CVSException with a status with code <code>COMMITTING_SYNC_INFO_FAILED</code>
 	 * if all the CVS sync information could not be written to disk.
 	 */
-	public void endOperation(IProgressMonitor monitor) throws CVSException {								
-		try {
-			IStatus status = SyncInfoCache.STATUS_OK;
-			if (lock.getDepth() == 1) {
-				status = commitCache(monitor);
+	public void endBatching(IProgressMonitor monitor) throws CVSException {
+		resourceLock.release(monitor);
+	}
+	
+	/* (non-Javadoc)
+	 * 
+	 * Callback which is invoked when the batching resource lock is released 
+	 * or when a flush is requested (see beginBatching(IResource)).
+	 * 
+	 * @see org.eclipse.team.internal.ccvs.core.syncinfo.ReentrantLock.IRunnableOnExit#run(org.eclipse.team.internal.ccvs.core.syncinfo.ReentrantLock.ThreadInfo, org.eclipse.core.runtime.IProgressMonitor)
+	 */
+	public void flush(final ThreadInfo info, IProgressMonitor monitor) throws CVSException {
+		if (info != null && !info.isEmpty()) {
+			try {
+				beginOperation();
+				ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
+					public void run(IProgressMonitor pm) throws CoreException {
+						IStatus status = commitCache(info, pm);
+						if (!status.isOK()) {
+							throw new CVSException(status);
+						}
+					}
+				}, null, monitor);
+			} catch (CoreException e) {
+				throw CVSException.wrapException(e);
+			} finally {
+				endOperation();
 			}
-			if (!status.isOK()) {
-				throw new CVSException(status);
-			}
-		} finally {
-			// ensure locks are released in the same order: cvs then workspace
-			lock.release();			
-			Platform.getJobManager().endRule();
 		}
 	}
 	
+	/*
+	 * Begin an access to the internal data structures of the synchronizer
+	 */
+	private void beginOperation() {
+		lock.acquire();
+	}
+	
+	/*
+	 * End an access to the internal data structures of the synchronizer
+	 */
+	private void endOperation() {						
+		lock.release();
+	}
+	
 	/**
-	 * Flushes unwritten sync information to disk.
+	 * Flush the sync information from the in-memery cache to disk and purge
+	 * the entries from the cache.
 	 * <p>
-	 * Recursively commits unwritten sync information for all resources 
-	 * below the root, and optionally purges the cached data from memory
+	 * Recursively flushes the sync information for all resources 
+	 * below the root to disk and purges the entries from memory
 	 * so that the next time it is accessed it will be retrieved from disk.
 	 * May flush more sync information than strictly needed, but never less.
 	 * </p>
-	 * <p>
-	 * Will throw a CVS Exception with a status with code = CVSStatus.DELETION_FAILED 
-	 * if the flush could not perform CVS folder deletions. In this case, all other
-	 * aspects of the operation succeeded.
-	 * </p>
 	 * 
-	 * @param root the root of the subtree to flush
-	 * @param purgeCache if true, purges the cache from memory as well
+	 * @param root the root of the subtree to purge
 	 * @param deep purge sync from child folders
 	 * @param monitor the progress monitor, may be null
 	 */
-	public void flush(IContainer root, boolean purgeCache, boolean deep, IProgressMonitor monitor) throws CVSException {
-		// flush unwritten sync info to disk
+	public void flush(IContainer root, boolean deep, IProgressMonitor monitor) throws CVSException {
 		monitor = Policy.monitorFor(monitor);
 		monitor.beginTask(null, 10);
 		try {
-			beginOperation(root, Policy.subMonitorFor(monitor, 1));
-			
-			IStatus status = commitCache(Policy.subMonitorFor(monitor, 7));
-			
-			// purge from memory too if we were asked to
-			if (purgeCache) {
-				sessionPropertyCache.purgeCache(root, deep);
-			} 
-	
-			// prepare for the operation again if we cut the last one short
-			prepareCache(Policy.subMonitorFor(monitor, 1));
-			
-			if (!status.isOK()) {
-				throw new CVSException(status);
+			beginBatching(root);
+			try {
+				beginOperation();
+				try {
+					// Flush changes to disk
+					resourceLock.flush(Policy.subMonitorFor(monitor, 7));
+				} finally {
+					// Purge the in-memory cache
+					sessionPropertyCache.purgeCache(root, deep);
+				}
+			} finally {
+				endOperation();
 			}
 		} finally {
-			endOperation(Policy.subMonitorFor(monitor, 1));
+			endBatching(Policy.subMonitorFor(monitor, 3));
 			monitor.done();
 		}
 	}
 
 	public void deconfigure(final IProject project, IProgressMonitor monitor) throws CVSException {
-		run(project, new ICVSRunnable() {
-			public void run(IProgressMonitor monitor) throws CVSException {
-				flush(project, true, true, monitor);
+		monitor = Policy.monitorFor(monitor);
+		monitor.beginTask(null, 100);
+		try {
+			beginBatching(project);
+			// Flush 
+			flush(project, true /* deep */, monitor);
 				
-				// forget about pruned folders however the top level pruned folder will have resource sync (e.g. 
-				// a line in the Entry file). As a result the folder is managed but is not a CVS folder.
-				synchronizerCache.purgeCache(project, true);
-			}
-		}, monitor);
+			// forget about pruned folders however the top level pruned folder will have resource sync (e.g. 
+			// a line in the Entry file). As a result the folder is managed but is not a CVS folder.
+			synchronizerCache.purgeCache(project, true);
+		} finally {
+			endBatching(Policy.subMonitorFor(monitor, 20));
+			monitor.done();
+		}
 	}
 
 	private void purgeCache(IResource resource, boolean deep) throws CVSException {
@@ -529,7 +589,7 @@
 		try {
 			for (int i = 0; i < roots.length; i++) {
 				IContainer root = roots[i];
-				flush(root, true, false /*don't flush children*/, null);
+				flush(root, false /*don't flush children*/, null);
 				List changedPeers = new ArrayList();
 				changedPeers.add(root);
 				changedPeers.addAll(Arrays.asList(root.members()));
@@ -548,37 +608,42 @@
 	public void prepareForDeletion(IResource resource) throws CVSException {
 		if (!resource.exists()) return;
 		try {
-			beginOperation(resource, null);
-			// Flush the dirty info for the resource and it's ancestors.
-			// Although we could be smarter, we need to do this because the
-			// deletion may fail.
-			adjustDirtyStateRecursively(resource, RECOMPUTE_INDICATOR);
-			if (resource.getType() == IResource.FILE) {
-				byte[] syncBytes = getSyncBytes(resource);
-				if (syncBytes != null) {
-					if (!ResourceSyncInfo.isAddition(syncBytes)) {
-						syncBytes = convertToDeletion(syncBytes);
+			beginBatching(resource);
+			try {
+				beginOperation();
+				// Flush the dirty info for the resource and it's ancestors.
+				// Although we could be smarter, we need to do this because the
+				// deletion may fail.
+				adjustDirtyStateRecursively(resource, RECOMPUTE_INDICATOR);
+				if (resource.getType() == IResource.FILE) {
+					byte[] syncBytes = getSyncBytes(resource);
+					if (syncBytes != null) {
+						if (!ResourceSyncInfo.isAddition(syncBytes)) {
+							syncBytes = convertToDeletion(syncBytes);
+							synchronizerCache.setCachedSyncBytes(resource, syncBytes);
+						}
+						resourceChanged(resource);
+					}
+				} else {
+					IContainer container = (IContainer)resource;
+					if (container.getType() == IResource.PROJECT) {
+						synchronizerCache.flush((IProject)container);
+					} else {
+						// Move the folder sync info into phantom space
+						FolderSyncInfo info = getFolderSync(container);
+						if (info == null) return;
+						synchronizerCache.setCachedFolderSync(container, info);
+						folderChanged(container);
+						// move the resource sync as well
+						byte[] syncBytes = getSyncBytes(resource);
 						synchronizerCache.setCachedSyncBytes(resource, syncBytes);
 					}
-					changedResources.add(resource);
 				}
-			} else {
-				IContainer container = (IContainer)resource;
-				if (container.getType() == IResource.PROJECT) {
-					synchronizerCache.flush((IProject)container);
-				} else {
-					// Move the folder sync info into phantom space
-					FolderSyncInfo info = getFolderSync(container);
-					if (info == null) return;
-					synchronizerCache.setCachedFolderSync(container, info);
-					changedFolders.add(container);
-					// move the resource sync as well
-					byte[] syncBytes = getSyncBytes(resource);
-					synchronizerCache.setCachedSyncBytes(resource, syncBytes);
-				}
+			} finally {
+				endOperation();
 			}
 		} finally {
-			endOperation(null);
+			endBatching(null);
 		}
 	}
 	
@@ -593,10 +658,15 @@
 	protected void handleDeleted(IResource resource) throws CVSException {
 		if (resource.exists()) return;
 		try {
-			beginOperation(resource, null);
-			adjustDirtyStateRecursively(resource, RECOMPUTE_INDICATOR);
+			beginBatching(resource);
+			try {
+				beginOperation();
+				adjustDirtyStateRecursively(resource, RECOMPUTE_INDICATOR);
+			} finally {
+				endOperation();
+			}
 		} finally {
-			endOperation(null);
+			endBatching(null);
 		}
 	}
 	
@@ -613,10 +683,11 @@
 	public void prepareForMoveDelete(IResource resource, IProgressMonitor monitor) throws CVSException {
 		// Move sync info to phantom space for the resource and all it's children
 		try {
+			monitor.beginTask(null, 100);
 			resource.accept(new IResourceVisitor() {
-				public boolean visit(IResource resource) throws CoreException {
+				public boolean visit(IResource innerResource) throws CoreException {
 					try {
-						prepareForDeletion(resource);
+						prepareForDeletion(innerResource);
 					} catch (CVSException e) {
 						CVSProviderPlugin.log(e);
 						throw new CoreException(e.getStatus());
@@ -626,6 +697,8 @@
 			});
 		} catch (CoreException e) {
 			throw CVSException.wrapException(e);
+		} finally {
+			monitor.done();
 		}
 		// purge the sync info to clear the session properties
 		purgeCache(resource, true);
@@ -639,11 +712,17 @@
 	 * @throws CVSException
 	 */
 	public void created(IResource resource) throws CVSException {
-		if (resource.getType() == IResource.FILE) {
-			created((IFile)resource);
-		} else if (resource.getType() == IResource.FOLDER) {
-			created((IFolder)resource);
+		try {
+			beginBatching(resource);
+			if (resource.getType() == IResource.FILE) {
+				created((IFile)resource);
+			} else if (resource.getType() == IResource.FOLDER) {
+				created((IFolder)resource);
+			}
+		} finally {
+			endBatching(null);
 		}
+
 	}
 	
 	/**
@@ -655,7 +734,7 @@
 	private void created(IFolder folder) throws CVSException {
 		try {
 			// set the dirty count using what was cached in the phantom it
-			beginOperation(folder, null);
+			beginOperation();
 			FolderSyncInfo folderInfo = synchronizerCache.getCachedFolderSync(folder);
 			byte[] syncBytes = synchronizerCache.getCachedSyncBytes(folder);
 			if (folderInfo != null && syncBytes != null) {
@@ -691,7 +770,7 @@
 			}
 		} finally {
 			try {
-				endOperation(null);
+				endOperation();
 			} finally {
 				synchronizerCache.flush(folder);
 			}
@@ -707,7 +786,7 @@
 	private void created(IFile file) throws CVSException {
 		try {
 			// set the dirty count using what was cached in the phantom it
-			beginOperation(file, null);
+			beginOperation();
 			byte[] syncBytes = synchronizerCache.getCachedSyncBytes(file);
 			if (syncBytes == null) return;
 			byte[] newBytes = getSyncBytes(file);
@@ -717,7 +796,7 @@
 			}
 		} finally {
 			try {
-				endOperation(null);
+				endOperation();
 			} finally {
 				synchronizerCache.setCachedSyncBytes(file, null);
 			}
@@ -801,14 +880,6 @@
 	}
 	
 	/**
-	 * Prepares the cache for a series of operations.
-	 *
-	 * @param monitor the progress monitor, may be null
-	 */
-	private void prepareCache(IProgressMonitor monitor) throws CVSException {
-	}
-	
-	/**
 	 * Commits the cache after a series of operations.
 	 * 
 	 * Will return STATUS_OK unless there were problems writting sync 
@@ -818,24 +889,26 @@
 	 * 
 	 * @param monitor the progress monitor, may be null
 	 */
-	private IStatus commitCache(IProgressMonitor monitor) {
-		if (changedFolders.isEmpty() && changedResources.isEmpty()) {
+	/* internal use only */ IStatus commitCache(ThreadInfo threadInfo, IProgressMonitor monitor) {
+		if (threadInfo.isEmpty()) {
 			return SyncInfoCache.STATUS_OK;
 		}
 		List errors = new ArrayList();
 		try {
 			/*** prepare operation ***/
 			// find parents of changed resources
+			IResource[] changedResources = threadInfo.getChangedResources();
+			IContainer[] changedFolders = threadInfo.getChangedFolders();
 			Set dirtyParents = new HashSet();
-			for(Iterator it = changedResources.iterator(); it.hasNext();) {
-				IResource resource = (IResource) it.next();
+			for (int i = 0; i < changedResources.length; i++) {
+				IResource resource = changedResources[i];
 				IContainer folder = resource.getParent();
 				dirtyParents.add(folder);
 			}
 			
 			monitor = Policy.monitorFor(monitor);
 			int numDirty = dirtyParents.size();
-			int numResources = changedFolders.size() + numDirty;
+			int numResources = changedFolders.length + numDirty;
 			monitor.beginTask(null, numResources);
 			if(monitor.isCanceled()) {
 				monitor.subTask(Policy.bind("EclipseSynchronizer.UpdatingSyncEndOperationCancelled")); //$NON-NLS-1$
@@ -845,8 +918,8 @@
 			
 			/*** write sync info to disk ***/
 			// folder sync info changes
-			for(Iterator it = changedFolders.iterator(); it.hasNext();) {
-				IContainer folder = (IContainer) it.next();
+			for (int i = 0; i < changedFolders.length; i++) {
+				IContainer folder = changedFolders[i];
 				if (folder.exists() && folder.getType() != IResource.ROOT) {
 					try {
 						FolderSyncInfo info = sessionPropertyCache.getCachedFolderSync(folder);
@@ -916,13 +989,13 @@
 			
 			/*** broadcast events ***/
 			monitor.subTask(Policy.bind("EclipseSynchronizer.NotifyingListeners")); //$NON-NLS-1$
-			changedResources.addAll(changedFolders);
-			changedResources.addAll(dirtyParents);	
-			IResource[] resources = (IResource[]) changedResources.toArray(
-				new IResource[changedResources.size()]);
+			Set allChanges = new HashSet();
+			allChanges.addAll(Arrays.asList(changedResources));
+			allChanges.addAll(Arrays.asList(changedFolders));
+			allChanges.addAll(dirtyParents);	
+			IResource[] resources = (IResource[]) allChanges.toArray(
+				new IResource[allChanges.size()]);
 			broadcastResourceStateChanges(resources);
-			changedResources.clear();
-			changedFolders.clear();
 			if ( ! errors.isEmpty()) {
 				MultiStatus status = new MultiStatus(CVSProviderPlugin.ID, 
 											CVSStatus.COMMITTING_SYNC_INFO_FAILED, 
@@ -972,7 +1045,7 @@
 	 */
 	private void setCachedSyncBytes(IResource resource, byte[] syncBytes) throws CVSException {
 		getSyncInfoCacheFor(resource).setCachedSyncBytes(resource, syncBytes);
-		changedResources.add(resource);
+		resourceChanged(resource);
 	}
 		
 	/**
@@ -1067,7 +1140,7 @@
 			Map infoMap = new HashMap();
 			for (int i = 0; i < infos.length; i++) {
 				NotifyInfo notifyInfo = infos[i];
-				infoMap.put(infos[i].getName(), infos[i]);
+				infoMap.put(notifyInfo.getName(), notifyInfo);
 			}
 			if (info == null) {
 				// if the info is null, remove the entry
@@ -1114,7 +1187,7 @@
 		Map infoMap = new HashMap();
 		for (int i = 0; i < infos.length; i++) {
 			NotifyInfo notifyInfo = infos[i];
-			infoMap.put(infos[i].getName(), infos[i]);
+			infoMap.put(notifyInfo.getName(), notifyInfo);
 		}
 		infoMap.remove(resource.getName());
 		NotifyInfo[] newInfos = new NotifyInfo[infoMap.size()];
@@ -1194,29 +1267,37 @@
 	}
 
 	public void copyFileToBaseDirectory(final IFile file, IProgressMonitor monitor) throws CVSException {
-		run(file, new ICVSRunnable() {
-			public void run(IProgressMonitor monitor) throws CVSException {
-				ResourceSyncInfo info = getResourceSync(file);
-				// The file must exist remotely and locally
-				if (info == null || info.isAdded() || info.isDeleted())
-					return;
-				SyncFileWriter.writeFileToBaseDirectory(file, monitor);
-				changedResources.add(file);
-			}
-		}, monitor);
+		monitor = Policy.monitorFor(monitor);
+		monitor.beginTask(null, 100);
+		try {
+			beginBatching(file);
+			ResourceSyncInfo info = getResourceSync(file);
+			// The file must exist remotely and locally
+			if (info == null || info.isAdded() || info.isDeleted())
+				return;
+			SyncFileWriter.writeFileToBaseDirectory(file, monitor);
+			resourceChanged(file);
+		} finally {
+			endBatching(Policy.subMonitorFor(monitor, 20));
+			monitor.done();
+		}
 	}
 	
 	public void restoreFileFromBaseDirectory(final IFile file, IProgressMonitor monitor) throws CVSException {
-		run(file, new ICVSRunnable() {
-			public void run(IProgressMonitor monitor) throws CVSException {
-				ResourceSyncInfo info = getResourceSync(file);
-				// The file must exist remotely
-				if (info == null || info.isAdded())
-					return;
-				SyncFileWriter.restoreFileFromBaseDirectory(file, monitor);
-				changedResources.add(file);
-			}
-		}, monitor);
+		monitor = Policy.monitorFor(monitor);
+		monitor.beginTask(null, 100);
+		try {
+			beginBatching(file);
+			ResourceSyncInfo info = getResourceSync(file);
+			// The file must exist remotely
+			if (info == null || info.isAdded())
+				return;
+			SyncFileWriter.restoreFileFromBaseDirectory(file, monitor);
+			resourceChanged(file);
+		} finally {
+			endBatching(Policy.subMonitorFor(monitor, 20));
+			monitor.done();
+		}
 	}
 	
 	public void deleteFileFromBaseDirectory(final IFile file, IProgressMonitor monitor) throws CVSException {
@@ -1261,12 +1342,12 @@
 		for (int i = 0; i < folders.length; i++) {
 			IContainer parent = folders[i];
 			try {
-				beginOperation(parent, null);
+				beginOperation();
 				cacheResourceSyncForChildren(parent);
 				cacheFolderSync(parent);
 				cacheFolderIgnores(parent);
 			} finally {
-				endOperation(null);
+				endOperation();
 			}
 		}
 	}
@@ -1287,9 +1368,9 @@
 			if (depth != IResource.DEPTH_ZERO) {
 				try {
 					resource.accept(new IResourceVisitor() {
-						public boolean visit(IResource resource) throws CoreException {
-							if (resource.getType() == IResource.FOLDER)
-								folders.add(resource);
+						public boolean visit(IResource innerResource) throws CoreException {
+							if (innerResource.getType() == IResource.FOLDER)
+								folders.add(innerResource);
 							// let the depth determine who we visit
 							return true;
 						}
@@ -1312,10 +1393,10 @@
 		monitor = Policy.monitorFor(monitor);
 		monitor.beginTask(null, 100);
 		try {
-			beginOperation(rootResource, Policy.subMonitorFor(monitor, 5));
-			job.run(Policy.subMonitorFor(monitor, 60));
+			beginBatching(rootResource);
+			job.run(Policy.subMonitorFor(monitor, 80));
 		} finally {
-			endOperation(Policy.subMonitorFor(monitor, 35));
+			endBatching(Policy.subMonitorFor(monitor, 20));
 			monitor.done();
 		}
 	}
@@ -1338,7 +1419,7 @@
 	private void adjustDirtyStateRecursively(IResource resource, String indicator) throws CVSException {
 		if (resource.getType() == IResource.ROOT) return;
 		try {
-			beginOperation(resource, null);
+			beginOperation();
 			
 			if (indicator == getDirtyIndicator(resource)) {
 				return;
@@ -1363,16 +1444,16 @@
 				adjustDirtyStateRecursively(parent, indicator);
 			} 
 		} finally {
-			endOperation(null);
+			endOperation();
 		}
 	}
 
 	protected String getDirtyIndicator(IResource resource) throws CVSException {
 		try {
-			beginOperation(resource, null);
+			beginOperation();
 			return getSyncInfoCacheFor(resource).getDirtyIndicator(resource);
 		} finally {
-			endOperation(null);
+			endOperation();
 		}
 	}
 	
@@ -1382,14 +1463,9 @@
 	 * Return true if the modification state was changed.
 	 */
 	protected void setDirtyIndicator(IResource resource, boolean modified) throws CVSException {
-		try {
-			beginOperation(resource, null);
-			String indicator = modified ? IS_DIRTY_INDICATOR : NOT_DIRTY_INDICATOR;
-			// set the dirty indicator and adjust the parent accordingly			
-			adjustDirtyStateRecursively(resource, indicator);
-		} finally {
-			endOperation(null);
-		}
+		String indicator = modified ? IS_DIRTY_INDICATOR : NOT_DIRTY_INDICATOR;
+		// set the dirty indicator and adjust the parent accordingly			
+		adjustDirtyStateRecursively(resource, indicator);
 	}
 
 	/**
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/ReentrantLock.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/ReentrantLock.java
index acf453d..1a43bfd 100644
--- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/ReentrantLock.java
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/ReentrantLock.java
@@ -10,75 +10,187 @@
  *******************************************************************************/
 package org.eclipse.team.internal.ccvs.core.syncinfo;
 
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
+import org.eclipse.team.internal.ccvs.core.CVSStatus;
 import org.eclipse.team.internal.ccvs.core.Policy;
 import org.eclipse.team.internal.ccvs.core.util.Assert;
 
 /**
  * Provides a per-thread nested locking mechanism. A thread can acquire a
- * lock and then call acquire() multiple times. Other threads that try
- * and acquire the lock will be blocked until the first thread releases all
- * it's nested locks.
+ * lock on a specific resource by calling acquire(). Subsequently, acquire() can be called
+ * multiple times on the resource or any of its children from within the same thread
+ * without blocking. Other threads that try
+ * and acquire the lock on those same resources will be blocked until the first 
+ * thread releases all it's nested locks.
+ * <p>
+ * The locking is managed by the platform via scheduling rules. This class simply 
+ * provides the nesting mechnism in order to allow the client to determine when
+ * the lock for the thread has been released. Therefore, this lock will block if
+ * another thread already locks the same resource.</p>
  */
 public class ReentrantLock {
 
 	private final static boolean DEBUG = Policy.DEBUG_THREADING;
-	private Thread thread;
-	private int nestingCount;
 	
-	private Set readOnlyThreads = new HashSet();
+	public class ThreadInfo {
+		private int nestingCount = 0;
+		private Set changedResources = new HashSet();
+		private Set changedFolders = new HashSet();
+		private IFlushOperation operation;
+		public ThreadInfo(IFlushOperation operation) {
+			this.operation = operation;
+		}
+		public void increment() {
+			nestingCount++;
+		}
+		public int decrement() {
+			nestingCount--;
+			return nestingCount;
+		}
+		public int getNestingCount() {
+			return nestingCount;
+		}
+		public void addChangedResource(IResource resource) {
+			changedResources.add(resource);
+		}
+		public void addChangedFolder(IContainer container) {
+			changedFolders.add(container);
+		}
+		public boolean isEmpty() {
+			return changedFolders.isEmpty() && changedResources.isEmpty();
+		}
+		public IResource[] getChangedResources() {
+			return (IResource[]) changedResources.toArray(new IResource[changedResources.size()]);
+		}
+		public IContainer[] getChangedFolders() {
+			return (IContainer[]) changedFolders.toArray(new IContainer[changedFolders.size()]);
+		}
+		public void flush(IProgressMonitor monitor) throws CVSException {
+			try {
+				operation.flush(this, monitor);
+			} catch (OutOfMemoryError e) {
+				throw e;
+			} catch (Error e) {
+				handleAbortedFlush(e);
+				throw e;
+			} catch (RuntimeException e) {
+				handleAbortedFlush(e);
+				throw e;
+			}
+			changedResources.clear();
+			changedFolders.clear();
+		}
+		private void handleAbortedFlush(Throwable t) {
+			CVSProviderPlugin.log(new CVSStatus(IStatus.ERROR, Policy.bind("ReentrantLock.9"), t)); //$NON-NLS-1$
+		}
+	}
+	
+	public interface IFlushOperation {
+		public void flush(ThreadInfo info, IProgressMonitor monitor) throws CVSException;
+	}
+	
+	private Map infos = new HashMap();
+	
 	
 	public ReentrantLock() {
-		this.thread = null;
-		this.nestingCount = 0;
 	}
 	
-	public synchronized void acquire() {
-		// stop early if we've been interrupted -- don't enter the lock anew
+	private ThreadInfo getThreadInfo() {
 		Thread thisThread = Thread.currentThread();
-
-		// race for access to the lock -- does not guarantee fairness
-		if (thread != thisThread) {
-			while (nestingCount != 0) {
-				try {
-					if(DEBUG) System.out.println("["+ thisThread.getName() + "] waiting for CVS synchronizer lock"); //$NON-NLS-1$ //$NON-NLS-2$
-					wait();
-				} catch(InterruptedException e) {
-					// keep waiting for the lock
-					if(DEBUG) System.out.println("["+ thisThread.getName() + "] interrupted in CVS synchronizer lock"); //$NON-NLS-1$ //$NON-NLS-2$
+		ThreadInfo info = (ThreadInfo)infos.get(thisThread);
+		return info;
+	}
+	
+	public synchronized void acquire(IResource resource, IFlushOperation operation) {
+		lock(resource);	
+		incrementNestingCount(resource, operation);
+	}
+	
+	private void incrementNestingCount(IResource resource, IFlushOperation operation) {
+		ThreadInfo info = getThreadInfo();
+		if (info == null) {
+			info = new ThreadInfo(operation);
+			Thread thisThread = Thread.currentThread();
+			infos.put(thisThread, info);
+			if(DEBUG) System.out.println("[" + thisThread.getName() + "] acquired CVS lock on " + resource.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+		info.increment();
+	}
+	
+	private void lock(IResource resource) {
+		// The scheduling rule is either the project or the resource's parent
+		ISchedulingRule rule;
+		if (resource.getType() == IResource.ROOT) {
+			// Never lock the whole workspace
+			rule = new ISchedulingRule() {
+				public boolean contains(ISchedulingRule innerRule) {
+					return false;
 				}
-			}
-			thread = thisThread;
-			if(DEBUG) System.out.println("[" + thisThread.getName() + "] acquired CVS synchronizer lock"); //$NON-NLS-1$ //$NON-NLS-2$
+				public boolean isConflicting(ISchedulingRule innerRule) {
+					return false;
+				}
+			};
+		} else  if (resource.getType() == IResource.PROJECT) {
+			rule = resource;
+		} else {
+			rule = resource.getParent();
 		}
-		nestingCount++;
+		Platform.getJobManager().beginRule(rule);
+	}
+
+	private void unlock() {
+		Platform.getJobManager().endRule();
 	}
 	
-	public synchronized void release() {
-		Thread thisThread = Thread.currentThread();
-		Assert.isLegal(thread == thisThread,
-			"Thread attempted to release a lock it did not own"); //$NON-NLS-1$
-		if (--nestingCount == 0) {
-			if(DEBUG) System.out.println("[" + thread.getName() + "] released CVS synchronizer lock"); //$NON-NLS-1$ //$NON-NLS-2$
-			thread = null;
-			notifyAll();
+	/**
+	 * Release the lock held on any resources by this thread. Execute the 
+	 * provided runnable if the lock is no longer held (i.e. nesting count is 0).
+	 * On exit, the scheduling rule is held by the lock until after the runnable
+	 * is run.
+	 */
+	public synchronized void release(IProgressMonitor monitor) throws CVSException {
+		ThreadInfo info = getThreadInfo();
+		Assert.isNotNull(info, "Unmatched acquire/release."); //$NON-NLS-1$
+		Assert.isTrue(info.getNestingCount() > 0, "Unmatched acquire/release."); //$NON-NLS-1$
+		if (info.decrement() == 0) {
+			Thread thisThread = Thread.currentThread();
+			if(DEBUG) System.out.println("[" + thisThread.getName() + "] released CVS lock"); //$NON-NLS-1$ //$NON-NLS-2$
+			infos.remove(thisThread);
+			info.flush(monitor);
+			unlock();
 		}
 	}
-	
-	public int getNestingCount() {
-		Thread thisThread = Thread.currentThread();
-		Assert.isLegal(thread == thisThread,
-			"Thread attempted to read nesting count of a lock it did not own"); //$NON-NLS-1$
-		return nestingCount;
+
+	public void folderChanged(IContainer folder) {
+		ThreadInfo info = getThreadInfo();
+		Assert.isNotNull(info, "Folder changed outside of resource lock"); //$NON-NLS-1$
+		info.addChangedFolder(folder);
 	}
-	
-	public boolean isReadOnly() {
-		return readOnlyThreads.contains(thread);
+
+	public void resourceChanged(IResource resource) {
+		ThreadInfo info = getThreadInfo();
+		Assert.isNotNull(info, "Folder changed outside of resource lock"); //$NON-NLS-1$
+		info.addChangedResource(resource);
 	}
-	
-	public void addReadOnlyThread(Thread thread) {
-		readOnlyThreads.add(thread);
+
+	/**
+	 * Flush any changes accumulated by the lock so far.
+	 */
+	public void flush(IProgressMonitor monitor) throws CVSException {
+		ThreadInfo info = getThreadInfo();
+		Assert.isNotNull(info, "Flush requested outside of resource lock"); //$NON-NLS-1$
+		info.flush(monitor);
 	}
 }
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/CVSDateFormatter.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/CVSDateFormatter.java
index e952b9c..fb42459 100644
--- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/CVSDateFormatter.java
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/CVSDateFormatter.java
@@ -38,18 +38,18 @@
 	static {
 		entryLineFormat.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$
 	}
-	static public Date serverStampToDate(String text) throws ParseException {
+	static synchronized public Date serverStampToDate(String text) throws ParseException {
 		serverFormat.setTimeZone(getTimeZone(text));
 		Date date = serverFormat.parse(text);
 		return date;
 	}
 
-	static public String dateToServerStamp(Date date) {
+	static synchronized public String dateToServerStamp(Date date) {
 		serverFormat.setTimeZone(TimeZone.getTimeZone("GMT"));//$NON-NLS-1$
 		return serverFormat.format(date) + " -0000"; //$NON-NLS-1$
 	}	
 	
-	static public Date entryLineToDate(String text) throws ParseException {
+	static synchronized public Date entryLineToDate(String text) throws ParseException {
 		try {
 			if (text.charAt(ENTRYLINE_TENS_DAY_OFFSET) == ' ') {
 				StringBuffer buf = new StringBuffer(text);
@@ -62,7 +62,7 @@
 		return entryLineFormat.parse(text);
 	}
 
-	static public String dateToEntryLine(Date date) {
+	static synchronized public String dateToEntryLine(Date date) {
 		if (date == null) return ""; //$NON-NLS-1$
 		String passOne = entryLineFormat.format(date);
 		if (passOne.charAt(ENTRYLINE_TENS_DAY_OFFSET) != '0') return passOne;
@@ -71,7 +71,7 @@
 		return passTwo.toString();
 	}
 	
-	static public String dateToNotifyServer(Date date) {
+	static synchronized public String dateToNotifyServer(Date date) {
 		serverFormat.setTimeZone(TimeZone.getTimeZone("GMT"));//$NON-NLS-1$
 		return serverFormat.format(date) + " GMT"; //$NON-NLS-1$
 	}
diff --git a/tests/org.eclipse.team.tests.cvs.core/launchConfigurations/One CVS Test.launch b/tests/org.eclipse.team.tests.cvs.core/launchConfigurations/One CVS Test.launch
new file mode 100644
index 0000000..3d3f131
--- /dev/null
+++ b/tests/org.eclipse.team.tests.cvs.core/launchConfigurations/One CVS Test.launch
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<launchConfiguration type="org.eclipse.pde.ui.JunitLaunchConfig">
+    <stringAttribute key="vmargs" value="-Declipse.cvs.properties=c:\eclipse\repository.properties -Declipse.cvs.testName=testFileAdditions"/>
+    <booleanAttribute key="askclear" value="false"/>
+    <booleanAttribute key="default" value="true"/>
+    <booleanAttribute key="clearws" value="true"/>
+    <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.team.tests.cvs.core"/>
+    <stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.jdt.debug.ui.javaSourceLocator"/>
+    <stringAttribute key="location0" value="C:\Eclipse\Latest-Eclipse-Drop\eclipse\runtime-test-workspace"/>
+    <stringAttribute
+        key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
+    <booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
+    <stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value=""/>
+    <stringAttribute key="application" value="org.eclipse.pde.junit.runtime.coretestapplication"/>
+    <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.team.tests.ccvs.core.provider.IsModifiedTests"/>
+    <stringAttribute key="progargs" value="-os win32 -ws win32 -arch x86 -nl en_CA"/>
+</launchConfiguration>
diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/cvsresources/BatchedTestSetup.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/cvsresources/BatchedTestSetup.java
index 1cf0024..4060f25 100644
--- a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/cvsresources/BatchedTestSetup.java
+++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/cvsresources/BatchedTestSetup.java
@@ -24,10 +24,10 @@
 	}
 
 	public void setUp() throws CVSException {
-		EclipseSynchronizer.getInstance().beginOperation(ResourcesPlugin.getWorkspace().getRoot(), null);
+		EclipseSynchronizer.getInstance().beginBatching(ResourcesPlugin.getWorkspace().getRoot());
 	}
 	
 	public void tearDown() throws CVSException {
-		EclipseSynchronizer.getInstance().endOperation(null);
+		EclipseSynchronizer.getInstance().endBatching(null);
 	}
 }
diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/provider/ConcurrencyTests.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/provider/ConcurrencyTests.java
index 119ae7a..aa86e96 100644
--- a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/provider/ConcurrencyTests.java
+++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/provider/ConcurrencyTests.java
@@ -24,6 +24,7 @@
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.jobs.IJobChangeEvent;
 import org.eclipse.core.runtime.jobs.IJobChangeListener;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
 import org.eclipse.jface.progress.IElementCollector;
 import org.eclipse.team.core.TeamException;
 import org.eclipse.team.internal.ccvs.core.ICVSRemoteFolder;
@@ -71,15 +72,10 @@
 			}
 		};
 		
-		IJobChangeListener listener = new IJobChangeListener() {
-			public void aboutToRun(IJobChangeEvent event) {}
-			public void awake(IJobChangeEvent event) {}
+		IJobChangeListener listener = new JobChangeAdapter() {
 			public void done(IJobChangeEvent event) {
 				done[0] = true;
 			}
-			public void running(IJobChangeEvent event) {}
-			public void scheduled(IJobChangeEvent event) {}
-			public void sleeping(IJobChangeEvent event) {}
 		};
 		FetchMembersOperation operation = new FetchMembersOperation(null, folder, collector);
 		operation.setCVSRunnableContext(new HeadlessCVSRunnableContext(listener));
diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/provider/LinkResourcesTest.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/provider/LinkResourcesTest.java
index 6dfd623..d7c8056 100644
--- a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/provider/LinkResourcesTest.java
+++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/provider/LinkResourcesTest.java
@@ -71,7 +71,7 @@
 	public void testLinkCVSFolder() throws CoreException, TeamException, IOException {
 		IProject source = createProject("testLinkSource", new String[] { "changed.txt", "deleted.txt", "folder1/", "folder1/a.txt" });
 		IProject sourceCopy = checkoutCopy(source, "copy");
-		EclipseSynchronizer.getInstance().flush(source, true, true, DEFAULT_MONITOR);
+		EclipseSynchronizer.getInstance().flush(source, true, DEFAULT_MONITOR);
 		IProject target = createProject("testLinkTarget", new String[] { "changed.txt", "deleted.txt", "folder1/", "folder1/a.txt" });
 		IFolder folder = target.getFolder("link");
 		folder.createLink(source.getLocation(), 0, null);