diff options
5 files changed, 415 insertions, 201 deletions
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/sync/RemoteContentsCache.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/sync/RemoteContentsCache.java index 67b985c0c..210ca912b 100644 --- a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/sync/RemoteContentsCache.java +++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/sync/RemoteContentsCache.java @@ -10,15 +10,7 @@ *******************************************************************************/ package org.eclipse.team.core.sync; -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -27,8 +19,8 @@ import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.jobs.ILock; import org.eclipse.team.core.TeamException; import org.eclipse.team.internal.core.Policy; import org.eclipse.team.internal.core.TeamPlugin; @@ -47,10 +39,12 @@ public class RemoteContentsCache { private static Map caches = new HashMap(); // String (local name) > RemoteContentsCache private String name; - private Map cacheFileNames; - private Map cacheFileTimes; + private Map cacheEntries; private long lastCacheCleanup; private int cacheDirSize; + + // Lock used to serialize the writting of cache contents + private ILock lock = Platform.getJobManager().newLock(); /** * Enables the use of remote contents caching for the given cacheId. The cache ID must be unique. @@ -89,31 +83,19 @@ public class RemoteContentsCache { * @param cacheId the unique Id of the cache * @throws TeamException if the cached contents could not be deleted from disk */ - public static void disableCache(String cacheId) throws TeamException { + public static void disableCache(String cacheId) { RemoteContentsCache cache = getCache(cacheId); if (cache == null) { - // There is no cahce to dispose of + // There is no cache to dispose of return; } - cache.deleteCacheDirectory(); caches.remove(cacheId); - } - - /** - * Return the <code>java.io.File</code> that contains the same contents as the remote resource - * identified by the <local name, qualified name> pair. It is up to the owner of the cache to use - * an appropriate qualified name that uniquely identified remote versions of a file. - * - * @param id - * @return - * @throws TeamException if the cache is not enabled - */ - public static synchronized File getFile(QualifiedName id) throws TeamException { - RemoteContentsCache cache = getCache(id.getLocalName()); - if (cache == null) { - throw new TeamException(Policy.bind("RemoteContentsCache.cacheNotEnabled", id.getLocalName())); //$NON-NLS-1$ + try { + cache.deleteCacheDirectory(); + } catch (TeamException e) { + // Log the exception and continue + TeamPlugin.log(e); } - return cache.getFile(id.getQualifier()); } /** @@ -130,179 +112,49 @@ public class RemoteContentsCache { } /** - * Return the <code>java.io.File</code> that contains the same contents as the remote resource - * identified by the given unique id. It is up to the owner of the cache to use - * an appropriate id that uniquely identified remote versions of a file. - * @param id - * @return - */ - public synchronized File getFile(String id) { - if (cacheFileNames == null) { - // This probably means that the cache has been disposed - throw new IllegalStateException(Policy.bind("RemoteContentsCache.cacheDisposed", name)); //$NON-NLS-1$ - } - String physicalPath; - if (cacheFileNames.containsKey(id)) { - // cache hit - physicalPath = (String)cacheFileNames.get(id); - registerHit(id); - } else { - // cache miss - physicalPath = String.valueOf(cacheDirSize++); - cacheFileNames.put(id, physicalPath); - registerHit(id); - clearOldCacheEntries(); - } - return getCacheFileForPhysicalPath(physicalPath); - } - - /** - * Return the InputStream that contains the cached contents for the given id. If an error occurs - * reading the cached contents then the cache entry is automatically removed to allow the contents - * to be refetched. - * - * @param id - * @return an InputStream containing the cached contents or null if no contents are cached - * @throws TeamException - */ - public synchronized InputStream getContents(String id) throws TeamException { - if (!cacheFileNames.containsKey(id)) { - // The contents are not cached - return null; - } - File ioFile = getFile(id); - try { - try { - if (ioFile.exists()) { - return new FileInputStream(ioFile); - } - } catch (IOException e) { - // Try to purge the cache and continue - purgeCacheFile(id); - throw e; - } - } catch (IOException e) { - // We will end up here if we couldn't read or delete the cache file - throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$ - } - // This can occur when there is no remote contents - return new ByteArrayInputStream(new byte[0]); - } - - /** - * Set the contents for the cache entry at the given id to the contents provided in the given input stream. Upon - * completion of this method the input stream will be closed even if an error occurred. If an error did occur, the - * cache entry for the given id will be cleared. - * - * @param id - * @param stream - * @param monitor - * @throws TeamException if an error occured opening or writing to the cache file - */ - public synchronized void setContents(String id, InputStream stream, IProgressMonitor monitor) throws TeamException { - File ioFile = getFile(id); - try { - - // Open the cache file for writing - OutputStream out; - try { - out = new BufferedOutputStream(new FileOutputStream(ioFile)); - } catch (FileNotFoundException e) { - throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$ - } - - // Transfer the contents - try { - try { - byte[] buffer = new byte[1024]; - int read; - while ((read = stream.read(buffer)) >= 0) { - Policy.checkCanceled(monitor); - out.write(buffer, 0, read); - } - } finally { - out.close(); - } - } catch (IOException e) { - // Make sure we don't leave the cache file around as it may not have the right contents - purgeCacheFile(id); - throw e; - } - } catch (IOException e) { - throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$ - } finally { - try { - stream.close(); - } catch (IOException e1) { - // Ignore close errors - } - } - } - - /** * Return whether the cache contains an entry for the given id. Register a hit if it does. * @param id the id of the cache entry * @return true if there are contents cached for the id */ - public boolean hasContents(String id) { - boolean contains = cacheFileNames.containsKey(id); - if (contains) { - registerHit(id); - } - return contains; - } - - /** - * Purge the cache entry for the given id. - * @param id - */ - public synchronized void purge(String id) { - purgeCacheFile(id); - } - - private File getCacheFileForPhysicalPath(String physicalPath) { - return new File(getCachePath().toFile(), physicalPath); + public boolean hasEntry(String id) { + return internalGetCacheEntry(id) != null; } - - private IPath getCachePath() { + + protected IPath getCachePath() { return getStateLocation().append(CACHE_DIRECTORY).append(name); } private IPath getStateLocation() { return TeamPlugin.getPlugin().getStateLocation(); } - - private void registerHit(String path) { - cacheFileTimes.put(path, Long.toString(new Date().getTime())); - } private void clearOldCacheEntries() { long current = new Date().getTime(); if ((lastCacheCleanup!=-1) && (current - lastCacheCleanup < CACHE_FILE_LIFESPAN)) return; List stale = new ArrayList(); - for (Iterator iter = cacheFileTimes.keySet().iterator(); iter.hasNext();) { - String f = (String) iter.next(); - long lastHit = Long.valueOf((String)cacheFileTimes.get(f)).longValue(); + for (Iterator iter = cacheEntries.values().iterator(); iter.hasNext();) { + RemoteContentsCacheEntry entry = (RemoteContentsCacheEntry) iter.next(); + long lastHit = entry.getLastAccessTimeStamp(); if ((current - lastHit) > CACHE_FILE_LIFESPAN){ - stale.add(f); + stale.add(entry); } } for (Iterator iter = stale.iterator(); iter.hasNext();) { - String f = (String) iter.next(); - purgeCacheFile(f); + RemoteContentsCacheEntry entry = (RemoteContentsCacheEntry) iter.next(); + entry.dispose(); } } - private void purgeCacheFile(String path) { - File f = getCacheFileForPhysicalPath((String)cacheFileNames.get(path)); + private void purgeFromCache(String id) { + RemoteContentsCacheEntry entry = (RemoteContentsCacheEntry)cacheEntries.get(id); + File f = entry.getFile(); try { deleteFile(f); } catch (TeamException e) { - // log the falied delete and continue - TeamPlugin.log(e); + // Ignore the deletion failure. + // A failure only really matters when purging the directory on startup } - cacheFileTimes.remove(path); - cacheFileNames.remove(path); + cacheEntries.remove(id); } private void createCacheDirectory() throws TeamException { @@ -314,21 +166,25 @@ public class RemoteContentsCache { if (! file.mkdirs()) { throw new TeamException(Policy.bind("RemoteContentsCache.fileError", file.getAbsolutePath())); //$NON-NLS-1$ } - cacheFileNames = new HashMap(); - cacheFileTimes = new HashMap(); + cacheEntries = new HashMap(); lastCacheCleanup = -1; cacheDirSize = 0; } private void deleteCacheDirectory() throws TeamException { + cacheEntries = null; + lastCacheCleanup = -1; + cacheDirSize = 0; IPath cacheLocation = getCachePath(); File file = cacheLocation.toFile(); if (file.exists()) { - deleteFile(file); + try { + deleteFile(file); + } catch (TeamException e) { + // Don't worry about problems deleting. + // The only case that matters is when the cache directory is created + } } - cacheFileNames = cacheFileTimes = null; - lastCacheCleanup = -1; - cacheDirSize = 0; } private void deleteFile(File file) throws TeamException { @@ -342,4 +198,59 @@ public class RemoteContentsCache { throw new TeamException(Policy.bind("RemoteContentsCache.fileError", file.getAbsolutePath())); //$NON-NLS-1$ } } + + /** + * Purge the given cache entry from the cache. This method should only be invoked from + * an instance of RemoteContentsCacheEntry after it has set it's state to DISPOSED. + * @param entry + */ + protected void purgeFromCache(RemoteContentsCacheEntry entry) { + purgeFromCache(entry.getId()); + } + + private RemoteContentsCacheEntry internalGetCacheEntry(String id) { + RemoteContentsCacheEntry entry = (RemoteContentsCacheEntry)cacheEntries.get(id); + if (entry != null) { + entry.registerHit(); + } + return entry; + } + + /** + * @param id the id that uniquely identifes the remote resource that is cached. + * @return + */ + public synchronized RemoteContentsCacheEntry getCacheEntry(String id) { + if (cacheEntries == null) { + // This probably means that the cache has been disposed + throw new IllegalStateException(Policy.bind("RemoteContentsCache.cacheDisposed", name)); //$NON-NLS-1$ + } + RemoteContentsCacheEntry entry = internalGetCacheEntry(id); + if (entry == null) { + // cache miss + entry = createCacheEntry(id); + } + return entry; + } + + private RemoteContentsCacheEntry createCacheEntry(String id) { + clearOldCacheEntries(); + String filePath = String.valueOf(cacheDirSize++); + RemoteContentsCacheEntry entry = new RemoteContentsCacheEntry(this, id, filePath); + cacheEntries.put(id, entry); + return entry; + } + + public String getName() { + return name; + } + + /** + * Provide access to the lock for the cache. This method should only be used by a cache entry. + * @return Returns the lock. + */ + protected ILock getLock() { + return lock; + } + } diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/sync/RemoteContentsCacheEntry.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/sync/RemoteContentsCacheEntry.java new file mode 100644 index 000000000..c54cca9c7 --- /dev/null +++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/sync/RemoteContentsCacheEntry.java @@ -0,0 +1,232 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.team.core.sync; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.team.core.TeamException; +import org.eclipse.team.internal.core.Policy; + +/** + * This class provides the implementation for the ICacheEntry + */ +public class RemoteContentsCacheEntry { + + public static final int UNINITIALIZED = 0; + public static final int READY = 1; + public static final int DISPOSED = 2; + + private String id; + private String filePath; + private RemoteContentsCache cache; + private byte[] syncBytes; + private int state = UNINITIALIZED; + private long lastAccess; + + public RemoteContentsCacheEntry(RemoteContentsCache cache, String id, String filePath) { + state = UNINITIALIZED; + this.cache = cache; + this.id = id; + this.filePath = filePath; + registerHit(); + } + + /* (non-Javadoc) + * @see org.eclipse.team.core.sync.ICacheEntry#getContents() + */ + public InputStream getContents() throws TeamException { + if (state != READY) return null; + registerHit(); + File ioFile = getFile(); + try { + try { + if (ioFile.exists()) { + return new FileInputStream(ioFile); + } + } catch (IOException e) { + // Try to purge the cache and continue + cache.purgeFromCache(this); + throw e; + } + } catch (IOException e) { + // We will end up here if we couldn't read or delete the cache file + throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$ + } + // This can occur when there is no remote contents + return new ByteArrayInputStream(new byte[0]); + } + + protected File getFile() { + return new File(cache.getCachePath().toFile(), filePath); + } + + /** + * Set the contents of for this cache entry. This method supports concurrency by only allowing + * one cache entry to be written at a time. In the case of two concurrent writes to the same cache entry, + * the contents from the first write is used and the content from subsequent writes is ignored. + * @param stream an InputStream that provides the contents to be cached + * @param monitor a progress monitor + * @throws TeamException if the entry is DISPOSED or an I/O error occurres + */ + public void setContents(InputStream stream, IProgressMonitor monitor) throws TeamException { + // Use a lock to only allow one write at a time + try { + beginOperation(); + internalSetContents(stream, monitor); + } finally { + endOperation(); + } + } + + private synchronized void endOperation() { + cache.getLock().release(); + } + + private synchronized void beginOperation() { + cache.getLock().acquire(); + } + + private void internalSetContents(InputStream stream, IProgressMonitor monitor) throws TeamException { + // if the state is DISPOSED then there is a problem + if (state == DISPOSED) { + throw new TeamException("Cache entry in {0} for {1} has been disposed" + cache.getName() + id); + } + // Otherwise, the state is UNINITIALIZED or READY so we can proceed + registerHit(); + File ioFile = getFile(); + try { + + // Open the cache file for writing + OutputStream out; + try { + if (state == UNINITIALIZED) { + out = new BufferedOutputStream(new FileOutputStream(ioFile)); + } else { + // If the entry is READY, the contents must have been read in another thread. + // We still need to red the contents but they can be ignored since presumably they are the same + out = new ByteArrayOutputStream(); + } + } catch (FileNotFoundException e) { + throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$ + } + + // Transfer the contents + try { + try { + byte[] buffer = new byte[1024]; + int read; + while ((read = stream.read(buffer)) >= 0) { + Policy.checkCanceled(monitor); + out.write(buffer, 0, read); + } + } finally { + out.close(); + } + } catch (IOException e) { + // Make sure we don't leave the cache file around as it may not have the right contents + cache.purgeFromCache(this); + throw e; + } + + // Mark the cache entry as ready + state = READY; + } catch (IOException e) { + throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$ + } finally { + try { + stream.close(); + } catch (IOException e1) { + // Ignore close errors + } + } + + } + + /* (non-Javadoc) + * @see org.eclipse.team.core.sync.ICacheEntry#getSyncBytes(byte[]) + */ + public byte[] getSyncBytes() { + return syncBytes; + } + + /** + * Set the sync bytes associated with the cached remote contents. + * This method is sychronized to ensure atomic setting of the bytes. + * @param bytes + */ + public synchronized void setSyncBytes(byte[] bytes) { + syncBytes = bytes; + } + + /* (non-Javadoc) + * @see org.eclipse.team.core.sync.ICacheEntry#getState() + */ + public int getState() { + return state; + } + + /* (non-Javadoc) + * @see org.eclipse.team.core.sync.ICacheEntry#getSize() + */ + public long getSize() { + if (state != READY) return 0; + File ioFile = getFile(); + if (ioFile.exists()) { + return ioFile.length(); + } + return 0; + } + + /* (non-Javadoc) + * @see org.eclipse.team.core.sync.ICacheEntry#getLastAccessTimeStamp() + */ + public long getLastAccessTimeStamp() { + return lastAccess; + } + + /** + * Registers a hit on this cache entry. This updates the last access timestamp. + * Thsi method is intended to only be invokded from inside this class or the cahce itself. + * Other clients should not use it. + */ + protected void registerHit() { + lastAccess = new Date().getTime(); + } + + public void dispose() { + // Use a lock to avoid changing state while another thread may be writting + try { + beginOperation(); + state = DISPOSED; + cache.purgeFromCache(this); + } finally { + endOperation(); + } + } + + /* (non-Javadoc) + * @see org.eclipse.team.core.sync.ICacheEntry#getId() + */ + public String getId() { + return id; + } +} diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSSyncInfo.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSSyncInfo.java index d907a414b..6a0ac543a 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSSyncInfo.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSSyncInfo.java @@ -185,7 +185,7 @@ public class CVSSyncInfo extends SyncInfo { } else { // We have conflictin additions. // We need to fetch the contents of the remote to get all the relevant information (timestamp, permissions) - // TODO: Do we really need to fetch the contents here? + // The most important thing we get is the keyword substitution mode which must be right to perform the commit remote.getContents(Policy.monitorFor(monitor)); info = remote.getSyncInfo().cloneMutable(); } 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 a7bff502e..9d661083f 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 @@ -11,7 +11,6 @@ package org.eclipse.team.internal.ccvs.core.resources; import java.io.ByteArrayInputStream; -import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; @@ -29,6 +28,7 @@ import org.eclipse.core.runtime.Path; import org.eclipse.team.core.TeamException; import org.eclipse.team.core.sync.IRemoteResource; import org.eclipse.team.core.sync.RemoteContentsCache; +import org.eclipse.team.core.sync.RemoteContentsCacheEntry; import org.eclipse.team.internal.ccvs.core.CVSException; import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin; import org.eclipse.team.internal.ccvs.core.CVSStatus; @@ -67,6 +67,8 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { private byte[] syncBytes; // cache the log entry for the remote file private ILogEntry entry; + // state that indicates that the handle is actively fetching content + private boolean fetching = false; /** * Static method which creates a file as a single child of its parent. @@ -179,6 +181,14 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { } /* package*/ void fetchContents(IProgressMonitor monitor) throws CVSException { + try { + fetching = true; + internalFetchContents(monitor); + } finally { + fetching = false; + } + } + /* package*/ void internalFetchContents(IProgressMonitor monitor) throws CVSException { monitor.beginTask(Policy.bind("RemoteFile.getContents"), 100);//$NON-NLS-1$ if (getRevision().equals(ResourceSyncInfo.ADDED_REVISION)) { // The revision of the remote file is not known so we need to use the tag to get the status of the file @@ -313,11 +323,12 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { * @see ICVSFile#getSize() */ public long getSize() { - File ioFile = getRemoteContentsCache().getFile(getCacheRelativePath()); - if (ioFile.exists()) { - return ioFile.length(); + if (fetching) return 0; + RemoteContentsCacheEntry entry = getCacheEntry(); + if (entry == null) { + return 0; } - return 0; + return entry.getSize(); } /** @@ -358,7 +369,7 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { * @see IManagedFile#setFileInfo(FileProperties) */ public void setSyncInfo(ResourceSyncInfo fileInfo, int modificationState) { - syncBytes = fileInfo.getBytes(); + setSyncBytes(fileInfo.getBytes(),modificationState); } /** @@ -371,10 +382,14 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { } public InputStream getContents() throws CVSException { - // Return the cached contents - InputStream cached = getCachedContents(); - if (cached != null) { - return cached; + if (!fetching) { + // Return the cached contents + if (isContentsCached()) { + InputStream cached = getCachedContents(); + if (cached != null) { + return cached; + } + } } // There was nothing cached so return an empty stream. // This is done to allow the contents to be fetched @@ -384,7 +399,13 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { private InputStream getCachedContents() throws CVSException { try { - return getRemoteContentsCache().getContents(getCacheRelativePath()); + RemoteContentsCacheEntry entry = getCacheEntry(); + byte[] newSyncBytes = entry.getSyncBytes(); + if (newSyncBytes != null) { + // Make sure the sync bytes match the content that is being accessed + syncBytes = newSyncBytes; + } + return entry.getContents(); } catch (TeamException e) { throw CVSException.wrapException(e); } @@ -392,7 +413,7 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { public void setContents(InputStream stream, int responseType, boolean keepLocalHistory, IProgressMonitor monitor) throws CVSException { try { - getRemoteContentsCache().setContents(getCacheRelativePath(), stream, monitor); + getCacheEntry().setContents(stream, monitor); } catch (TeamException e) { throw CVSException.wrapException(e); } @@ -420,7 +441,12 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { */ public boolean isContentsCached() { if (getRevision().equals(ResourceSyncInfo.ADDED_REVISION)) return false; - return getRemoteContentsCache().hasContents(getCacheRelativePath()); + String cacheRelativePath = getCacheRelativePath(); + if (!getRemoteContentsCache().hasEntry(cacheRelativePath)) { + return false; + } + RemoteContentsCacheEntry entry = getRemoteContentsCache().getCacheEntry(cacheRelativePath); + return entry.getState() == RemoteContentsCacheEntry.READY; } /* @@ -579,8 +605,16 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile { /** * @see org.eclipse.team.internal.ccvs.core.ICVSFile#setSyncBytes(byte[]) */ - public void setSyncBytes(byte[] syncBytes, int modificationState) throws CVSException { - setSyncInfo(new ResourceSyncInfo(syncBytes), ICVSFile.UNKNOWN); + public void setSyncBytes(byte[] syncBytes, int modificationState) { + if (fetching) { + RemoteContentsCacheEntry entry = getCacheEntry(); + entry.setSyncBytes(syncBytes); + } + this.syncBytes = syncBytes; + } + + private RemoteContentsCacheEntry getCacheEntry() { + return getRemoteContentsCache().getCacheEntry(getCacheRelativePath()); } public String toString() { diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSWorkspaceSubscriberTest.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSWorkspaceSubscriberTest.java index 72985d296..ced37be9a 100644 --- a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSWorkspaceSubscriberTest.java +++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSWorkspaceSubscriberTest.java @@ -1270,9 +1270,46 @@ public class CVSWorkspaceSubscriberTest extends CVSSyncSubscriberTest { assertSyncEquals("testNestedMarkAsMerged sync check", project, new String[] { "folder2/", "folder2/file.txt", "folder2/file2.txt"}, true, new int[] { - SyncInfo.IN_SYNC, - SyncInfo.OUTGOING | SyncInfo.CHANGE, + SyncInfo.IN_SYNC, + SyncInfo.OUTGOING | SyncInfo.CHANGE, + SyncInfo.CONFLICTING | SyncInfo.ADDITION + }); + } + + public void testMarkAsMergedOnBinaryFile() throws TeamException, CoreException, InvocationTargetException, InterruptedException { + // Create a project and checkout a copy + IProject project = createProject(new String[] { "file1.txt"}); + IProject copy = checkoutCopy(project, "-copy"); + // Add the same binary file to both projects to create a conflicting addition + buildResources(project, new String[] {"binary.gif"}, false); + addResources(copy, new String[] {"binary.gif"}, true); + assertIsBinary(copy.getFile("binary.gif")); + assertSyncEquals("testMarkAsMergedOnBinaryFile sync check", project, + new String[] {"binary.gif"}, + true, new int[] { + SyncInfo.CONFLICTING | SyncInfo.ADDITION + }); + markAsMerged(getSubscriber(), project, new String[] {"binary.gif"}); + assertSyncEquals("testMarkAsMergedOnBinaryFile sync check", project, + new String[] {"binary.gif"}, + true, new int[] { + SyncInfo.OUTGOING | SyncInfo.CHANGE + }); + assertIsBinary(project.getFile("binary.gif")); + // Unmanage the file and do it again + // This tests the case were the contents are already cached locally + CVSWorkspaceRoot.getCVSFileFor(project.getFile("binary.gif")).unmanage(DEFAULT_MONITOR); + assertSyncEquals("testMarkAsMergedOnBinaryFile sync check", project, + new String[] {"binary.gif"}, + true, new int[] { SyncInfo.CONFLICTING | SyncInfo.ADDITION }); + markAsMerged(getSubscriber(), project, new String[] {"binary.gif"}); + assertSyncEquals("testMarkAsMergedOnBinaryFile sync check", project, + new String[] {"binary.gif"}, + true, new int[] { + SyncInfo.OUTGOING | SyncInfo.CHANGE + }); + assertIsBinary(project.getFile("binary.gif")); } } |