### Eclipse Workspace Patch 1.0 #P org.eclipse.team.cvs.core Index: src/org/eclipse/team/internal/ccvs/core/CVSMessages.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSMessages.java,v retrieving revision 1.16 diff -u -r1.16 CVSMessages.java --- src/org/eclipse/team/internal/ccvs/core/CVSMessages.java 24 Apr 2009 09:33:21 -0000 1.16 +++ src/org/eclipse/team/internal/ccvs/core/CVSMessages.java 19 Jul 2010 13:21:35 -0000 @@ -165,6 +165,7 @@ public static String CVSRepositoryLocation_hostRequired; public static String CVSRepositoryLocation_rootRequired; public static String CVSRepositoryLocation_noAuthenticator; + public static String CVSRepositoryLocation_mixedRepository; public static String Util_timeout; public static String Util_processTimeout; @@ -188,6 +189,7 @@ public static String CVSProvider_Scrubbing_projects_1; public static String CVSProvider_Creating_projects_2; + public static String Resource_not_in_CVS; public static String EclipseFile_Problem_deleting_resource; public static String EclipseFile_Problem_accessing_resource; public static String EclipseFile_Problem_writing_resource; Index: src/org/eclipse/team/internal/ccvs/core/CVSStatus.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSStatus.java,v retrieving revision 1.33 diff -u -r1.33 CVSStatus.java --- src/org/eclipse/team/internal/ccvs/core/CVSStatus.java 24 Apr 2009 09:33:21 -0000 1.33 +++ src/org/eclipse/team/internal/ccvs/core/CVSStatus.java 19 Jul 2010 13:21:35 -0000 @@ -11,6 +11,7 @@ package org.eclipse.team.internal.ccvs.core; import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.osgi.util.NLS; import org.eclipse.team.core.TeamStatus; @@ -45,6 +46,7 @@ // Path for resource related status private ICVSFolder commandRoot; + private IPath commandPath; // Server information private ICVSRepositoryLocation cvsLocation; @@ -65,6 +67,11 @@ this(severity, code, message, null, resource); } + public CVSStatus(int severity, int code, String message, IPath path) { + this(severity, code, message, null, (IResource) null); + commandPath = path; + } + public CVSStatus(int severity, int code, String message, Throwable t, ICVSFolder commandRoot) { super(severity, CVSProviderPlugin.ID, code, message, t, null); this.commandRoot = commandRoot; @@ -94,6 +101,9 @@ if (commandRoot != null) { message = NLS.bind(CVSMessages.CVSStatus_messageWithRoot, new String[] { commandRoot.getName(), message }); } + if (commandPath != null) { + message = NLS.bind(CVSMessages.CVSStatus_messageWithRoot, new String[] { commandPath.toString(), message }); + } return message; } Index: src/org/eclipse/team/internal/ccvs/core/CVSTeamProvider.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSTeamProvider.java,v retrieving revision 1.193 diff -u -r1.193 CVSTeamProvider.java --- src/org/eclipse/team/internal/ccvs/core/CVSTeamProvider.java 9 Oct 2009 07:34:09 -0000 1.193 +++ src/org/eclipse/team/internal/ccvs/core/CVSTeamProvider.java 19 Jul 2010 13:21:35 -0000 @@ -685,6 +685,7 @@ } private IStatus internalValidateCreateLink(IResource resource) { + /* Allow creating linked resources of files already included in source repository. ICVSFolder cvsFolder = CVSWorkspaceRoot.getCVSFolderFor(resource.getParent().getFolder(new Path(resource.getName()))); try { if (cvsFolder.isCVSFolder()) { @@ -700,7 +701,8 @@ } catch (CVSException e) { CVSProviderPlugin.log(e); return e.getStatus(); - } + }*/ + return Status.OK_STATUS; } Index: src/org/eclipse/team/internal/ccvs/core/ICVSResource.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/ICVSResource.java,v retrieving revision 1.17 diff -u -r1.17 ICVSResource.java --- src/org/eclipse/team/internal/ccvs/core/ICVSResource.java 10 May 2006 18:42:10 -0000 1.17 +++ src/org/eclipse/team/internal/ccvs/core/ICVSResource.java 19 Jul 2010 13:21:35 -0000 @@ -12,7 +12,9 @@ import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.team.core.RepositoryProvider; import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo; /** @@ -40,6 +42,14 @@ public String getName(); /** + * Returns the location of a resource in the file system, if it exists. + * + * @return the location of the resource this handle represents. It can + * be null. + */ + public IPath getLocation(); + + /** * Answers if this resource has CVS synchronization information associated * with it. * @@ -170,4 +180,10 @@ * @return boolean */ public boolean isModified(IProgressMonitor monitor) throws CVSException; + + /** + * Method isModified. + * @return boolean + */ + public RepositoryProvider getProvider(); } Index: src/org/eclipse/team/internal/ccvs/core/messages.properties =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/messages.properties,v retrieving revision 1.172 diff -u -r1.172 messages.properties --- src/org/eclipse/team/internal/ccvs/core/messages.properties 24 Apr 2009 09:33:21 -0000 1.172 +++ src/org/eclipse/team/internal/ccvs/core/messages.properties 19 Jul 2010 13:21:36 -0000 @@ -162,7 +162,7 @@ CVSRepositoryLocation_hostRequired=A host name is required to make a connection CVSRepositoryLocation_rootRequired=A root path is required to make a connection CVSRepositoryLocation_noAuthenticator=No CVS authenticator is registered - +CVSRepositoryLocation_mixedRepository=Projects resources belong to different cvs repositories Util_timeout=A timeout occurred connecting to host {0} Util_processTimeout=A timeout occurred executing command ''{0}'' @@ -189,6 +189,7 @@ CVSProvider_Scrubbing_projects_1=Scrubbing projects CVSProvider_Creating_projects_2=Creating projects +Resource_not_in_CVS=Not CVS File: {0} ({1}) EclipseFile_Problem_deleting_resource=Problem deleting resource: {0}. {1} EclipseFile_Problem_accessing_resource=Problem accessing resource: {0}. {1} Perform a Refresh. EclipseFile_Problem_writing_resource=Problem writing resource ''{0}''. {1} Index: src/org/eclipse/team/internal/ccvs/core/client/Command.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/Command.java,v retrieving revision 1.78 diff -u -r1.78 Command.java --- src/org/eclipse/team/internal/ccvs/core/client/Command.java 21 Jun 2007 14:56:11 -0000 1.78 +++ src/org/eclipse/team/internal/ccvs/core/client/Command.java 19 Jul 2010 13:21:36 -0000 @@ -13,8 +13,8 @@ import java.util.*; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IResource; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.osgi.util.NLS; import org.eclipse.team.internal.ccvs.core.*; @@ -309,11 +309,13 @@ // print the invocation string to the console if (session.isOutputToConsole() || Policy.isDebugProtocol()) { IPath commandRootPath; - IResource resource = session.getLocalRoot().getIResource(); - if (resource == null) { - commandRootPath = Path.EMPTY; - } else { - commandRootPath = resource.getFullPath(); + commandRootPath = session.getLocalRoot().getLocation(); + if (commandRootPath == null) + commandRootPath = Path.EMPTY; + else { + IContainer[] containers = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocationURI(URIUtil.toURI(commandRootPath)); + if (containers.length > 0) + commandRootPath = containers[0].getFullPath(); } String line = constructCommandInvocationString(commandRootPath, gOptions, lOptions, arguments); ConsoleListeners.getInstance().commandInvoked(session, line); Index: src/org/eclipse/team/internal/ccvs/core/client/Session.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/Session.java,v retrieving revision 1.103 diff -u -r1.103 Session.java --- src/org/eclipse/team/internal/ccvs/core/client/Session.java 16 Mar 2007 21:03:49 -0000 1.103 +++ src/org/eclipse/team/internal/ccvs/core/client/Session.java 19 Jul 2010 13:21:37 -0000 @@ -12,18 +12,10 @@ package org.eclipse.team.internal.ccvs.core.client; import java.io.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - +import java.util.*; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.*; import org.eclipse.osgi.util.NLS; import org.eclipse.team.core.RepositoryProvider; @@ -921,13 +913,10 @@ } // If there is a provider, use the providers setting for watch/edit try { - IResource resource = getLocalRoot().getIResource(); - if (resource != null && resource.getType() != IResource.ROOT) { - RepositoryProvider provider = RepositoryProvider.getProvider(resource.getProject(), CVSProviderPlugin.getTypeId()); + RepositoryProvider provider = getLocalRoot().getProvider(); if (provider != null) { return ((CVSTeamProvider) provider).isWatchEditEnabled(); } - } } catch (CVSException e) { CVSProviderPlugin.log(e); } Index: src/org/eclipse/team/internal/ccvs/core/client/Update.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/Update.java,v retrieving revision 1.36 diff -u -r1.36 Update.java --- src/org/eclipse/team/internal/ccvs/core/client/Update.java 16 Mar 2007 21:03:49 -0000 1.36 +++ src/org/eclipse/team/internal/ccvs/core/client/Update.java 19 Jul 2010 13:21:37 -0000 @@ -11,11 +11,8 @@ package org.eclipse.team.internal.ccvs.core.client; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; -import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.osgi.util.NLS; @@ -124,19 +121,15 @@ */ protected boolean shouldRetrieveAbsentDirectories(Session session) { // Look for absent directories if enabled and the option is not already included - IResource resource = null; RepositoryProvider provider = null; // If there is a provider, use the providers setting try { - resource = session.getLocalRoot().getIResource(); - if (resource != null) { - provider = RepositoryProvider.getProvider(resource.getProject(), CVSProviderPlugin.getTypeId()); + provider = session.getLocalRoot().getProvider(); if (provider != null) { if (((CVSTeamProvider)provider).getFetchAbsentDirectories()) { return true; } } - } } catch (CVSException e) { CVSProviderPlugin.log(e); } Index: src/org/eclipse/team/internal/ccvs/core/resources/CVSFileSystemRoot.java =================================================================== RCS file: src/org/eclipse/team/internal/ccvs/core/resources/CVSFileSystemRoot.java diff -N src/org/eclipse/team/internal/ccvs/core/resources/CVSFileSystemRoot.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/team/internal/ccvs/core/resources/CVSFileSystemRoot.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.core.resources; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.team.core.RepositoryProvider; +import org.eclipse.team.internal.ccvs.core.*; + +/** + * This class provides static methods for checking out projects from a repository + * into the local file system (not necessarily mappable to a workspace resource) + * and for converting IPaths into CVSRespources and sync trees. + * Instances of this class represent a local workspace root (i.e. a project). + */ +public class CVSFileSystemRoot { + + public CVSFileSystemRoot(){ + } + + public static ICVSFolder getCVSFolderFor(IPath resource, RepositoryProvider repositoryProvider) { + return new FileSystemFolder(resource, repositoryProvider); + } + + public static ICVSFile getCVSFileFor(IPath resource, RepositoryProvider repositoryProvider) { + return new FileSystemFile(resource, repositoryProvider); + } + + public static ICVSResource getCVSResourceFor(IPath resource, RepositoryProvider repositoryProvider) { + if (resource.toFile().isFile()) + return getCVSFileFor(resource, repositoryProvider); + else + return getCVSFolderFor(resource, repositoryProvider); + } +} Index: src/org/eclipse/team/internal/ccvs/core/resources/FileSystemFile.java =================================================================== RCS file: src/org/eclipse/team/internal/ccvs/core/resources/FileSystemFile.java diff -N src/org/eclipse/team/internal/ccvs/core/resources/FileSystemFile.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/team/internal/ccvs/core/resources/FileSystemFile.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,595 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * Red Hat Incorporated - is/setExecutable() code + *******************************************************************************/ +package org.eclipse.team.internal.ccvs.core.resources; + +import java.io.*; +import java.util.*; + +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.*; +import org.eclipse.team.core.RepositoryProvider; +import org.eclipse.team.core.TeamException; +import org.eclipse.team.internal.ccvs.core.*; +import org.eclipse.team.internal.ccvs.core.client.Session; +import org.eclipse.team.internal.ccvs.core.syncinfo.*; + +/** + * Represents handles to CVS resource on the local file system. Synchronization + * information is taken from the CVS sub directories. + */ +public class FileSystemFile extends FileSystemResource implements ICVSFile { + + private static final IPath PROJECT_META_DATA_PATH = new Path(".project");//$NON-NLS-1$ + + /** + * Create a handle based on the given local resource. + */ + protected FileSystemFile(IPath resource, + RepositoryProvider repositoryProvider) { + super(resource, repositoryProvider); + } + + /* + * @see ICVSResource#delete() + */ + public void delete() throws CVSException { + resource.toFile().delete(); + } + + public long getSize() { + return getIOFile().length(); + } + + public InputStream getContents() throws CVSException { + try { + return new FileInputStream(resource.toFile()); + } catch (IOException e) { + throw CVSException.wrapException(e); // + } + } + + /* + * @see ICVSFile#getTimeStamp() + */ + public Date getTimeStamp() { + long timestamp = getFileInfo().getLastModified(); + if( timestamp == IResource.NULL_STAMP) { + // If there is no file, return the same timestamp as ioFile.lastModified() would + return new Date(0L); + } + return new Date((timestamp/1000)*1000); + } + + private IFileInfo getFileInfo() { + try { + return EFS.getStore(URIUtil.toURI(resource)).fetchInfo(); + } catch (CoreException e) { + return EFS.createFileInfo(); + } + } + + /* + * @see ICVSFile#setTimeStamp(Date) + */ + public void setTimeStamp(Date date) throws CVSException { + long time; + if (date == null) { + time = System.currentTimeMillis(); + } else { + time = date.getTime(); + } + getFileInfo().setLastModified(time); + } + + /* + * @see ICVSFile#isModified() + */ + public boolean isModified(IProgressMonitor monitor) throws CVSException { + + // ignore the monitor, there is no valuable progress to be shown when + // calculating the dirty state for files. It is relatively fast. + + if (!exists()) { + return getSyncBytes() != null; + } + return true; + } + + /* + * @see ICVSResource#accept(ICVSResourceVisitor) + */ + public void accept(ICVSResourceVisitor visitor) throws CVSException { + visitor.visitFile(this); + } + + /* + * @see ICVSResource#accept(ICVSResourceVisitor, boolean) + */ + public void accept(ICVSResourceVisitor visitor, boolean recurse) throws CVSException { + visitor.visitFile(this); + } + + /* + * This is to be used by the Copy handler. The filename of the form .#filename + */ + public void copyTo(String filename) throws CVSException { + try { + InputStream in = new FileInputStream(resource.toFile()); + File targetFile = resource.removeLastSegments(1).append(filename).toFile(); + if (targetFile.exists()) + targetFile.delete(); + OutputStream out = new FileOutputStream(targetFile); + + // Transfer bytes from in to out + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + in.close(); + out.close(); + } catch(IOException e) { + throw CVSException.wrapException(e); + } + } + + /* + * @see ICVSResource#getRemoteLocation() + */ + public String getRemoteLocation(ICVSFolder stopSearching) throws CVSException { + return getParent().getRemoteLocation(stopSearching) + SEPARATOR + getName(); + } + + /* + * @see ICVSFile#setReadOnly() + */ + public void setContents(InputStream stream, int responseType, boolean keepLocalHistory, IProgressMonitor monitor) throws CVSException { + try { + if (PROJECT_META_DATA_PATH.equals(resource.lastSegment())) { + responseType = UPDATED; + } + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int nextByte; + while ((nextByte = stream.read() ) != -1) { + os.write((byte) nextByte); + } + switch (responseType) { + case UPDATED: + if (exists()) { + writefile(os); + break; + } + case CREATED: // creating a new file so it should not exist locally + case MERGED: // merging contents into a file that exists locally + case UPDATE_EXISTING: // creating a new file so it should exist locally + writefile(os); + break; + } + } catch(IOException e) { + throw CVSException.wrapException(e); + } + } + + private void writefile(ByteArrayOutputStream os) + throws FileNotFoundException, IOException { + RandomAccessFile raFile = new RandomAccessFile(getIOFile(), "rw"); //$NON-NLS-1$ + raFile.write(os.toByteArray()); + raFile.setLength(os.toByteArray().length); + raFile.close(); + } + + /* + * @see ICVSFile#setReadOnly() + */ + public void setReadOnly(boolean readOnly) throws CVSException { + try { + IFileStore fileStore = EFS.getStore(URIUtil.toURI(resource)); + if (fileStore != null) { + IFileInfo fileInfo = fileStore.fetchInfo(); + if (fileInfo != null) + fileInfo.setAttribute(EFS.ATTRIBUTE_READ_ONLY, readOnly); + } + } catch (CoreException e) { + CVSException.wrapException(e); + } + } + + /* + * @see ICVSFile#isReadOnly() + */ + public boolean isReadOnly() throws CVSException { + return !getIOFile().canWrite(); + } + + /* + * @see ICVSFile#setExecutable() + */ + public void setExecutable(boolean executable) throws CVSException { + try { + IFileStore fileStore = EFS.getStore(URIUtil.toURI(resource)); + if (fileStore != null) { + IFileInfo fileInfo = fileStore.fetchInfo(); + if (fileInfo != null) + fileInfo.setAttribute(EFS.ATTRIBUTE_EXECUTABLE, executable); + } + } catch (CoreException e) { + CVSException.wrapException(e); + } + } + + /* + * @see ICVSFile#isExectuable() + */ + public boolean isExecutable() throws CVSException { + try { + IFileStore fileStore = EFS.getStore(URIUtil.toURI(resource)); + if (fileStore != null) { + IFileInfo fileInfo = fileStore.fetchInfo(); + if (fileInfo != null) + return fileInfo.getAttribute(EFS.ATTRIBUTE_EXECUTABLE); + } + } catch (CoreException e) { + CVSException.wrapException(e); + } + return false; + } + + /* + * To allow accessing size and timestamp for the underlying java.io.File + */ + private File getIOFile() { + return resource.toFile(); + } + /** + * @see ICVSFile#getLogEntries(IProgressMonitor) + */ + public ILogEntry[] getLogEntries(IProgressMonitor monitor) throws TeamException { + byte[] syncBytes = getSyncBytes(); + if(syncBytes != null && !ResourceSyncInfo.isAddition(syncBytes)) { + ICVSRemoteResource remoteFile = CVSWorkspaceRoot.getRemoteResourceFor(this); + if (remoteFile != null) + return ((ICVSRemoteFile)remoteFile).getLogEntries(monitor); + } + return new ILogEntry[0]; + } + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#setNotifyInfo(NotifyInfo) + */ + public void setNotifyInfo(NotifyInfo info) throws CVSException { + if (isManaged()) { + EclipseSynchronizer.getInstance().setNotifyInfo(resource, info); + // On an edit, the base should be cached + // On an unedit, the base should be restored (and cleared?) + // On a commit, the base should be cleared + } + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#getNotifyInfo() + */ + public NotifyInfo getNotifyInfo() throws CVSException { + if (isManaged()) { + return EclipseSynchronizer.getInstance().getNotifyInfo(resource); + } + return null; + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#setNotifyInfo(NotifyInfo) + */ + public void setBaserevInfo(BaserevInfo info) throws CVSException { + if (isManaged()) { + if (info == null) { + EclipseSynchronizer.getInstance().deleteBaserevInfo(resource); + EclipseSynchronizer.getInstance().deleteFileFromBaseDirectory(resource, null); + } else + EclipseSynchronizer.getInstance().setBaserevInfo(resource, info); + } + } + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#getNotifyInfo() + */ + public BaserevInfo getBaserevInfo() throws CVSException { + if (isManaged()) { + return EclipseSynchronizer.getInstance().getBaserevInfo(resource); + } + return null; + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#checkout(int) + */ + public void edit(final int notifications, boolean notifyForWritable, IProgressMonitor monitor) throws CVSException { + if (!notifyForWritable && !isReadOnly()) return; + run(new ICVSRunnable() { + public void run(IProgressMonitor monitor) throws CVSException { + byte[] syncBytes = getSyncBytes(); + if (syncBytes == null || ResourceSyncInfo.isAddition(syncBytes)) return; + + // convert the notifications to internal form + char[] internalFormat; + if (notifications == NO_NOTIFICATION) { + internalFormat = null; + } else if (notifications == NOTIFY_ON_ALL) { + internalFormat = NotifyInfo.ALL; + } else { + List notificationCharacters = new ArrayList(); + if ((notifications & NOTIFY_ON_EDIT) >0) + notificationCharacters.add(new Character(NotifyInfo.EDIT)); + if ((notifications & NOTIFY_ON_UNEDIT) >0) + notificationCharacters.add(new Character(NotifyInfo.UNEDIT)); + if ((notifications & NOTIFY_ON_COMMIT) >0) + notificationCharacters.add(new Character(NotifyInfo.COMMIT)); + internalFormat = new char[notificationCharacters.size()]; + for (int i = 0; i < internalFormat.length; i++) { + internalFormat[i] = ((Character)notificationCharacters.get(i)).charValue(); + } + } + + // record the notification + NotifyInfo notifyInfo = new NotifyInfo(getName(), NotifyInfo.EDIT, new Date(), internalFormat); + setNotifyInfo(notifyInfo); + + // Only record the base if the file is not modified + if (!isModified(null)) { + EclipseSynchronizer.getInstance().copyFileToBaseDirectory(resource, monitor); + setBaserevInfo(new BaserevInfo(getName(), ResourceSyncInfo.getRevision(syncBytes))); + } + + try { + // allow editing + setReadOnly(false); + } catch (CVSException e) { + // Just log and keep going + CVSProviderPlugin.log(e); + } + } + }, monitor); + + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#uncheckout() + */ + public void unedit(IProgressMonitor monitor) throws CVSException { + if (isReadOnly()) return; + run(new ICVSRunnable() { + public void run(IProgressMonitor monitor) throws CVSException { + // record the notification + NotifyInfo info = getNotifyInfo(); + if (info != null && info.getNotificationType() == NotifyInfo.EDIT) { + info = null; + } else { + info = new NotifyInfo(getName(), NotifyInfo.UNEDIT, new Date(), null); + } + setNotifyInfo(info); + + if (isModified(null)) { + ResourceSyncInfo syncInfo = getSyncInfo(); + BaserevInfo baserevInfo = getBaserevInfo(); + EclipseSynchronizer.getInstance().restoreFileFromBaseDirectory(resource, monitor); + // reset any changes that may have been merged from the server + if (!syncInfo.getRevision().equals(baserevInfo.getRevision())) { + MutableResourceSyncInfo newInfo = syncInfo.cloneMutable(); + newInfo.setRevision(baserevInfo.getRevision()); + newInfo.setTimeStamp(getTimeStamp()); + newInfo.setDeleted(false); + setSyncInfo(newInfo, ICVSFile.CLEAN); + } else { + // an unedited file is no longer modified + // external files have no modified states + //EclipseSynchronizer.getInstance().setModified(resource, CLEAN); + } + } else { + // We still need to report a state change + setSyncBytes(getSyncBytes(), ICVSFile.CLEAN); + } + setBaserevInfo(null); + + try { + // prevent editing + setReadOnly(true); + } catch (CVSException e) { + // Just log and keep going + CVSProviderPlugin.log(e); + } + } + }, monitor); + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#notificationCompleted() + */ + public void notificationCompleted() throws CVSException { + EclipseSynchronizer.getInstance().deleteNotifyInfo(resource); + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#getPendingNotification() + */ + public NotifyInfo getPendingNotification() throws CVSException { + return getNotifyInfo(); + } + + /* (non-Javadoc) + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#checkedIn(java.lang.String) + */ + public void checkedIn(String entryLine, boolean commit) throws CVSException { + ResourceSyncInfo oldInfo = getSyncInfo(); + ResourceSyncInfo newInfo = null; + int modificationState = ICVSFile.CLEAN; + if (entryLine == null) { + // cvs commit: the file contents matched the server contents so no entry line was sent + if (oldInfo == null) return; + // We should never make the timestamp go backwards so we'll set + // the entry line timestamp to match that of the file + if(! oldInfo.isAdded()) { + MutableResourceSyncInfo mutable = oldInfo.cloneMutable(); + mutable.setTimeStamp(getTimeStamp(), true /* clear merged */); + newInfo = mutable; + } + // (modified = false) the file will be no longer modified + } else if (oldInfo == null) { + // cvs add: addition of a file + newInfo = new ResourceSyncInfo(entryLine, null); + // an added file should show up as modified + modificationState = ICVSFile.DIRTY; + } else { + // cvs commit: commit of a changed file + // cvs update: update of a file whose contents match the server contents + Date timeStamp; + if (commit) { + // This is a commit. Put the file timestamp in the entry + timeStamp = getTimeStamp(); + } else { + // This is an update. We need to change the tiemstamp in the + // entry file to match the file timestamp returned by Java + timeStamp = oldInfo.getTimeStamp(); + if (timeStamp == null) { + timeStamp = getTimeStamp(); + } else { + // First, set the timestamp of the file to the timestamp from the entry + // There is a chance this will do nothing as the call to Java on some + // file systems munges the timestamps + setTimeStamp(timeStamp); + // To compensate for the above, reset the timestamp in the entry + // to match the timestamp in the file + timeStamp = getTimeStamp(); + } + } + newInfo = new ResourceSyncInfo(entryLine, timeStamp); + + } + //see bug 106876 + if (newInfo != null){ + CVSTag tag = newInfo.getTag(); + if(tag != null && CVSEntryLineTag.BASE.getName().equals(tag.getName())){ + newInfo = newInfo.cloneMutable(); + ((MutableResourceSyncInfo)newInfo).setTag(oldInfo.getTag()); + } + setSyncInfo(newInfo, modificationState); + } + clearCachedBase(); + } + + private void clearCachedBase() throws CVSException { + BaserevInfo base = getBaserevInfo(); + if (base != null) { + setBaserevInfo(null); + try { + setReadOnly(true); + } catch (CVSException e) { + // Just log and keep going + CVSProviderPlugin.log(e); + } + } else { + // Check to see if watch-edit is enabled for the project + if (repositoryProvider != null && ((CVSTeamProvider) repositoryProvider).isWatchEditEnabled()) { + try { + setReadOnly(true); + } catch (CVSException e) { + // Just log and keep going + CVSProviderPlugin.log(e); + } + } + } + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSResource#unmanage(org.eclipse.core.runtime.IProgressMonitor) + */ + public void unmanage(IProgressMonitor monitor) throws CVSException { + run(new ICVSRunnable() { + public void run(IProgressMonitor monitor) throws CVSException { + FileSystemFile.super.unmanage(monitor); + clearCachedBase(); + } + }, monitor); + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#isEdited() + */ + public boolean isEdited() throws CVSException { + return EclipseSynchronizer.getInstance().isEdited(resource); + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSResource#setSyncInfo(org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo) + */ + public void setSyncInfo(ResourceSyncInfo info, int modificationState) throws CVSException { + setSyncBytes(info.getBytes(), info, modificationState); + } + + /** + * @see ICVSResource#isFolder() + */ + public boolean isFolder() { + return false; + } + + /** + * @see org.eclipse.team.internal.ccvs.core.resources.EclipseResource#setSyncBytes(byte[], int) + */ + public void setSyncBytes(byte[] syncBytes, int modificationState) throws CVSException { + setSyncBytes(syncBytes, null, modificationState); + } + + /* + * @see org.eclipse.team.internal.ccvs.core.resources.EclipseResource#setSyncBytes(byte[], int) + */ + private void setSyncBytes(byte[] syncBytes, ResourceSyncInfo info, int modificationState) throws CVSException { + Assert.isNotNull(syncBytes); + setSyncBytes(syncBytes); + // external files have no modified states + //EclipseSynchronizer.getInstance().setModified(this, modificationState); + } + + public void handleModification(boolean forAddition) throws CVSException { + // external files have no modified states + } + + /* (non-Javadoc) + * @see org.eclipse.team.internal.ccvs.core.ICVSResource#getRepositoryRelativePath() + */ + public String getRepositoryRelativePath() throws CVSException { + if (!isManaged()) return null; + String parentPath = getParent().getRepositoryRelativePath(); + if (parentPath == null) return null; + return parentPath + Session.SERVER_SEPARATOR + getName(); + } + + protected boolean isDirty() throws CVSException { + boolean dirty; + byte[] syncBytes = getSyncBytes(); + if (syncBytes == null) { + dirty = exists(); + } else { + // isMerged() must be called because when a file is updated and merged by the cvs server the timestamps + // are equal. Merged files should however be reported as dirty because the user should take action and commit + // or review the merged contents. + if(ResourceSyncInfo.isAddition(syncBytes) || ResourceSyncInfo.isMerge(syncBytes) || !exists()) { + dirty = true; + } else { + // TODO: non-optimal as ResourceSyncInfo is created each time + ResourceSyncInfo info = new ResourceSyncInfo(syncBytes); + dirty = !getTimeStamp().equals(info.getTimeStamp()); + } + } + return dirty; + } + +} + + Index: src/org/eclipse/team/internal/ccvs/core/resources/FileSystemFolder.java =================================================================== RCS file: src/org/eclipse/team/internal/ccvs/core/resources/FileSystemFolder.java diff -N src/org/eclipse/team/internal/ccvs/core/resources/FileSystemFolder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/team/internal/ccvs/core/resources/FileSystemFolder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,363 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.core.resources; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; +import org.eclipse.team.core.RepositoryProvider; +import org.eclipse.team.internal.ccvs.core.*; +import org.eclipse.team.internal.ccvs.core.client.Session; +import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo; +import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo; +import org.eclipse.team.internal.ccvs.core.util.Util; + +/** + * Implements the ICVSFolder interface on top of an + * instance of the ICVSFolder interface + * + * @see ICVSFolder + */ +class FileSystemFolder extends FileSystemResource implements ICVSFolder { + + protected FileSystemFolder(IPath resource, RepositoryProvider repositoryProvider) { + super(resource, repositoryProvider); + } + + /** + * @see ICVSFolder#members(int) + */ + public ICVSResource[] members(int flags) throws CVSException { + final List result = new ArrayList(); + File resources[] = resource.toFile().listFiles(); + boolean includeFiles = (((flags & FILE_MEMBERS) != 0) || ((flags & (FILE_MEMBERS | FOLDER_MEMBERS)) == 0)); + boolean includeFolders = (((flags & FOLDER_MEMBERS) != 0) || ((flags & (FILE_MEMBERS | FOLDER_MEMBERS)) == 0)); + boolean includeManaged = (((flags & MANAGED_MEMBERS) != 0) || ((flags & (MANAGED_MEMBERS | UNMANAGED_MEMBERS | IGNORED_MEMBERS)) == 0)); + boolean includeUnmanaged = (((flags & UNMANAGED_MEMBERS) != 0) || ((flags & (MANAGED_MEMBERS | UNMANAGED_MEMBERS | IGNORED_MEMBERS)) == 0)); + boolean includeIgnored = ((flags & IGNORED_MEMBERS) != 0); + boolean includeExisting = (((flags & EXISTING_MEMBERS) != 0) || ((flags & (EXISTING_MEMBERS | PHANTOM_MEMBERS)) == 0)); + boolean includePhantoms = (((flags & PHANTOM_MEMBERS) != 0) || ((flags & (EXISTING_MEMBERS | PHANTOM_MEMBERS)) == 0)); + for (int i = 0; i < resources.length; i++) { + File resource = resources[i]; + if ((includeFiles && resource.isFile()) + || (includeFolders && resource.isDirectory())) { + boolean exists = resource.exists(); + if ((includeExisting && exists) || (includePhantoms && !exists)) { + ICVSResource cvsResource = CVSFileSystemRoot.getCVSResourceFor(new Path(resource.getAbsolutePath()), repositoryProvider); + boolean includeResource = false; + if ((includeManaged && includeUnmanaged && includeIgnored)) { + includeResource = true; + } else { + boolean isManaged = cvsResource.isManaged(); + if (isManaged && includeManaged) { + includeResource = true; + } else if (exists) { + boolean isIgnored = cvsResource.isIgnored(); + if (isIgnored && includeIgnored) { + includeResource = true; + } else if (! isManaged && ! isIgnored && includeUnmanaged) { + includeResource = true; + } + } + } + if (includeResource) { + result.add(cvsResource); + } + } + } + } + return (ICVSResource[]) result.toArray(new ICVSResource[result.size()]); + } + + public boolean isManaged() throws CVSException { + boolean result = super.isManaged(); + if (!result) { + FolderSyncInfo info = getFolderSyncInfo(); + if (info != null) // returns true is this is the root of the file system CVS repository + result = info.getRepository().equals(resource.lastSegment()); + } + return result; + } + + /** + * @see ICVSFolder#createFolder(String) + */ + public ICVSFolder getFolder(String name) throws CVSException { + if ((CURRENT_LOCAL_FOLDER.equals(name)) || ((CURRENT_LOCAL_FOLDER + SEPARATOR).equals(name))) + return this; + IPath folderLocation = resource.append(name); + IProject project = repositoryProvider.getProject(); + if (project != null) { + IContainer[]folders = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocationURI(URIUtil.toURI(folderLocation)); + if (folders.length > 0) { + for (int i = 0; i < folders.length; i++) { + if (folders[i].getProject().equals(project)) + return new EclipseFolder(folders[i]); + } + } + } + return new FileSystemFolder(folderLocation, repositoryProvider); + } + + /** + * @see ICVSFolder#createFile(String) + */ + public ICVSFile getFile(String name) throws CVSException { + IPath fileLocation = resource.append(name); + IProject project = repositoryProvider.getProject(); + if (project != null) { + IFile[]files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(URIUtil.toURI(fileLocation)); + if (files.length > 0) { + for (int i = 0; i < files.length; i++) { + if (files[i].exists() && files[i].getProject().equals(project)) + return new EclipseFile(files[i]); + } + } + } + return new FileSystemFile(fileLocation, repositoryProvider); + } + + /** + * @see ICVSFolder#mkdir() + */ + public void mkdir() throws CVSException { + resource.toFile().mkdir(); + // EclipseSynchronizer.getInstance().created(resource);; + } + + /** + * @see ICVSResource#isFolder() + */ + public boolean isFolder() { + return true; + } + + /** + * @see ICVSFolder#acceptChildren(ICVSResourceVisitor) + */ + public void acceptChildren(ICVSResourceVisitor visitor) throws CVSException { + + // Visit files and then folders + ICVSResource[] subFiles = members(FILE_MEMBERS); + for (int i=0; inull if resource + * the resource. + * + * @see ICVSResource#getParent() + */ + public ICVSFolder getParent() { + if (resource.isRoot()) + return null; + IPath newResource = resource.removeLastSegments(1); + return new FileSystemFolder(newResource, repositoryProvider); + } + + /* + * @see ICVSResource#getName() + */ + public String getName() { + return resource.lastSegment(); + } + + /* + * @see ICVSResource#isIgnored() + */ + public boolean isIgnored() throws CVSException { + // a managed resource is never ignored + if(isManaged() || resource.isRoot()) { + return false; + } + + // always ignore CVS + String name = getName(); + if (name.equals("CVS")) return true; //$NON-NLS-1$ + + /* do something with the ignore files + // check ignore patterns from the .cvsignore file. + if(EclipseSynchronizer.getInstance().isIgnored(resource)) { + return true; + } + */ + + // check the parent, if the parent is ignored or mapped to CVSROOT/Emptydir + // then this resource is ignored also + ICVSFolder parent = getParent(); + if(parent==null) return false; + if (parent.isIgnored()) return true; + FolderSyncInfo info = parent.getFolderSyncInfo(); + if (info == null) return false; + return info.isVirtualDirectory(); + } + + /* + * @see ICVSResource#setIgnoredAs(String) + */ + public void setIgnoredAs(final String pattern) throws CVSException { + Assert.isTrue(false); + } + + /* + * @see ICVSResource#isManaged() + */ + public boolean isManaged() throws CVSException { + return isManaged(getSyncBytes()); + } + + /* + * Helper method that captures the sematics of isManaged given a ResourceSyncInfo + */ + public boolean isManaged(byte[] syncBytes) { + return syncBytes != null; + } + + /** + * Two ManagedResources are equal, if there cvsResources are + * equal (and that is, if the point to the same file) + */ + public boolean equals(Object obj) { + + if (!(obj instanceof ICVSResource)) { + return false; + } else { + return getLocation().equals(((ICVSResource) obj).getLocation()); + } + } + + /* + * @see ICVSResource#getPath() + */ + public String getPath() { + return resource.toString(); + } + + public IPath getLocation() { + return resource; + } + + /* + * @see ICVSResource#isFolder() + */ + public boolean isFolder() { + return resource.toFile().isDirectory(); + } + + /* + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#getSyncBytes() + */ + public byte[] getSyncBytes() throws CVSException { + return EclipseSynchronizer.getInstance().getSyncBytes(resource); + } + + /* + * @see org.eclipse.team.internal.ccvs.core.ICVSFile#setSyncBytes(byte[]) + */ + public void setSyncBytes(byte[] syncBytes) throws CVSException { + if (getParent().isCVSFolder()) { + IResource workspaceResource = getIResource(); + if (workspaceResource != null) + EclipseSynchronizer.getInstance().setSyncBytes(workspaceResource, syncBytes); + else + EclipseSynchronizer.getInstance().setSyncBytes(resource, syncBytes); + } + } + + /* + * @see ICVSResource#getSyncInfo() + */ + public ResourceSyncInfo getSyncInfo() throws CVSException { + return EclipseSynchronizer.getInstance().getResourceSync(resource); + } + + /* + * Implement the hashcode on the underlying strings, like it is done in the equals. + */ + public int hashCode() { + return getPath().hashCode(); + } + + /* + * Give the pathname back + */ + public String toString() { + return getPath(); + } + + /* + * @see ICVSResource#unmanage() + */ + public void unmanage(IProgressMonitor monitor) throws CVSException { + EclipseSynchronizer.getInstance().deleteResourceSync(resource); + } + + /* + * @see Comparable#compareTo(Object) + */ + public int compareTo(Object arg0) { + ICVSResource other = (ICVSResource)arg0; + return getLocation().toString().compareTo(other.getLocation().toString()); + } + + /** + * @see org.eclipse.team.internal.ccvs.core.ICVSResource#getIResource() + */ + public IResource getIResource() { + if (!cachedResourceInitialized) { + cachedResourceInitialized = true; + URI uri = URIUtil.toURI(resource); + IFile files[] = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(uri); + if (files.length > 0 && files[0].exists()) + cachedResource = files[0]; + else { + IContainer containers[] = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocationURI(uri); + if (containers.length > 0 && containers[0].exists()) + cachedResource = containers[0]; + } + } + return cachedResource; + } + + /** + * Called by a resource change listener when a resource is changed or added. This allows + * CVS resources to adjust any internal state based on the change. + * + * @param forAddition modification is an addition + * @throws CVSException + */ + public abstract void handleModification(boolean forAddition) throws CVSException; + + public void run(final ICVSRunnable job, IProgressMonitor monitor) throws CVSException { + final IProject project = repositoryProvider.getProject(); + if (project != null) { + final CVSException[] error = new CVSException[1]; + try { + // Do not use a scheduling rule in the workspace run since one + // will be obtained by the EclipseSynchronizer + ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() { + public void run(IProgressMonitor monitor) throws CoreException { + try { + EclipseSynchronizer.getInstance().run(project, job, monitor); + } catch(CVSException e) { + error[0] = e; + } + } + }, null /* no rule */, 0, monitor); + } catch(CoreException e) { + throw CVSException.wrapException(e); + } + if(error[0]!=null) { + throw error[0]; + } + } + else { + final CVSException[] error = new CVSException[1]; + try { + job.run(monitor); + } catch(CVSException e) { + error[0] = e; + } + if(error[0]!=null) { + throw error[0]; + } + } + } + + public RepositoryProvider getProvider() { + return repositoryProvider; + } +} Index: src/org/eclipse/team/internal/ccvs/core/util/SyncFileWriter.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/SyncFileWriter.java,v retrieving revision 1.71 diff -u -r1.71 SyncFileWriter.java --- src/org/eclipse/team/internal/ccvs/core/util/SyncFileWriter.java 1 Feb 2010 10:52:22 -0000 1.71 +++ src/org/eclipse/team/internal/ccvs/core/util/SyncFileWriter.java 19 Jul 2010 13:21:40 -0000 @@ -15,8 +15,7 @@ import java.net.URI; import java.util.*; -import org.eclipse.core.filesystem.EFS; -import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.*; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.osgi.util.NLS; @@ -72,12 +71,23 @@ return null; } + // process Entries file contents + String[] entries = readLines(cvsSubDir.getFile(ENTRIES)); + String[] entriesLog = readLines(cvsSubDir.getFile(ENTRIES_LOG)); + return readAllResourceSync(parent.getFullPath().toOSString(), entries, entriesLog); + } + + /** + * Reads the CVS/Entries, CVS/Entries.log and CVS/Permissions files from the + * specified folder and returns ResourceSyncInfo instances for the data stored therein. + * If the folder does not have a CVS subdirectory then null is returned. + */ + private static byte[][] readAllResourceSync(String userPath, String[] entries, String[] entriesLog) throws CVSException { if (Policy.DEBUG_METAFILE_CHANGES) { - System.out.println("Reading Entries file for " + parent.getFullPath()); //$NON-NLS-1$ + System.out.println("Reading Entries file for " + userPath); //$NON-NLS-1$ } // process Entries file contents - String[] entries = readLines(cvsSubDir.getFile(ENTRIES)); if (entries == null) return null; Map infos = new TreeMap(); for (int i = 0; i < entries.length; i++) { @@ -89,13 +99,12 @@ } catch (CVSException e) { // There was a problem parsing the entry line. // Log the problem and skip the entry - CVSProviderPlugin.log(new CVSStatus(IStatus.ERROR, NLS.bind(CVSMessages.SyncFileWriter_0, new String[] { parent.getFullPath().toString() }), e)); + CVSProviderPlugin.log(new CVSStatus(IStatus.ERROR, NLS.bind(CVSMessages.SyncFileWriter_0, new String[] { userPath }), e)); } } } // process Entries.log file contents - String[] entriesLog = readLines(cvsSubDir.getFile(ENTRIES_LOG)); if (entriesLog != null) { for (int i = 0; i < entriesLog.length; i++) { String line = entriesLog[i]; @@ -121,6 +130,20 @@ return result; } + public static byte[][] readAllResourceSync(IPath parent) throws CVSException { + IPath cvsSubDir = getCVSSubdirectory(parent); + + if (!cvsSubDir.toFile().exists()){ + return null; + } + + // process Entries file contents + String[] entries = readLines(cvsSubDir.append(ENTRIES)); + // process Entries.log file contents + String[] entriesLog = readLines(cvsSubDir.append(ENTRIES_LOG)); + return readAllResourceSync(parent.toOSString(), entries, entriesLog); + } + private static boolean folderExists(IFolder cvsSubDir) throws CVSException { try { URI uri = cvsSubDir.getLocationURI(); @@ -136,6 +159,10 @@ return false; } + private static boolean folderExists(IPath cvsSubDir) throws CVSException { + return cvsSubDir.toFile().exists(); + } + public static void writeAllResourceSync(IContainer parent, byte[][] infos) throws CVSException { try { if (Policy.DEBUG_METAFILE_CHANGES) { @@ -159,6 +186,34 @@ throw CVSException.wrapException(e); } } + + public static void writeAllResourceSync(IPath parent, byte[][] infos) throws CVSException { + try { + if (Policy.DEBUG_METAFILE_CHANGES) { + System.out.println("Writing Entries file for folder " + parent.toOSString()); //$NON-NLS-1$ + } + IPath cvsSubDir = getCVSSubdirectory(parent); + if (!cvsSubDir.toFile().exists()) { + cvsSubDir.toFile().mkdir(); + } + + // format file contents + String[] entries = new String[infos.length]; + for (int i = 0; i < infos.length; i++) { + byte[] info = infos[i]; + entries[i] = new String(info); + } + + // write Entries + writeLines(cvsSubDir.append(ENTRIES), entries); + + // delete Entries.log + cvsSubDir.append(ENTRIES_LOG).toFile().delete(); + } catch(IOException e) { + throw CVSException.wrapException(e); + } + } + /** * Reads the CVS/Root, CVS/Repository, CVS/Tag, and CVS/Entries.static files from * the specified folder and returns a FolderSyncInfo instance for the data stored therein. @@ -211,6 +266,48 @@ } /** + * Reads the CVS/Root, CVS/Repository, CVS/Tag, and CVS/Entries.static files from + * the specified folder and returns a FolderSyncInfo instance for the data stored therein. + * If the folder does not have a CVS subdirectory then null is returned. + */ + public static FolderSyncInfo readFolderSync(IPath folder) throws CVSException { + IPath cvsSubDir = getCVSSubdirectory(folder); + + if (!folderExists(cvsSubDir)){ + return null; + } + + if (Policy.DEBUG_METAFILE_CHANGES) { + System.out.println("Reading Root/Repository files for " + folder.toOSString()); //$NON-NLS-1$ + } + + // read CVS/Root + String root = readFirstLine(cvsSubDir.append(ROOT)); + if (root == null) return null; + + // read CVS/Repository + String repository = readFirstLine(cvsSubDir.append(REPOSITORY)); + if (repository == null) return null; + + // read CVS/Tag + String tag = readFirstLine(cvsSubDir.append(TAG)); + if (Policy.DEBUG_METAFILE_CHANGES && tag != null) { + System.out.println("Reading Tag file for " + folder.toOSString()); //$NON-NLS-1$ + } + CVSTag cvsTag = (tag != null) ? new CVSEntryLineTag(tag) : null; + + // read Entries.Static + String staticDir = readFirstLine(cvsSubDir.append(STATIC)); + if (Policy.DEBUG_METAFILE_CHANGES && staticDir != null) { + System.out.println("Reading Static file for " + folder.toOSString()); //$NON-NLS-1$ + } + boolean isStatic = (staticDir != null); + + // return folder sync + return new FolderSyncInfo(repository, root, cvsTag, isStatic); + } + + /** * Writes the CVS/Root, CVS/Repository, CVS/Tag, and CVS/Entries.static files to the * specified folder using the data contained in the specified FolderSyncInfo instance. */ @@ -265,6 +362,62 @@ } /** + * Writes the CVS/Root, CVS/Repository, CVS/Tag, and CVS/Entries.static files to the + * specified folder using the data contained in the specified FolderSyncInfo instance. + */ + public static void writeFolderSync(IPath folder, FolderSyncInfo info) throws CVSException { + try { + if (Policy.DEBUG_METAFILE_CHANGES) { + System.out.println("Writing Root/Respository files for " + folder.toOSString()); //$NON-NLS-1$ + } + IPath cvsSubDir = createCVSSubdirectory(folder); + + // write CVS/Root + writeLines(cvsSubDir.append(ROOT), new String[] {info.getRoot()}); + + // write CVS/Repository + writeLines(cvsSubDir.append(REPOSITORY), new String[] {info.getRepository()}); + + // write CVS/Tag + IPath tagFile = cvsSubDir.append(TAG); + if (info.getTag() != null) { + if (Policy.DEBUG_METAFILE_CHANGES) { + System.out.println("Writing Tag file for " + folder.toOSString()); //$NON-NLS-1$ + } + writeLines(tagFile, new String[] {info.getTag().toEntryLineFormat(false)}); + } else { + if(tagFile.toFile().exists()) { + if (Policy.DEBUG_METAFILE_CHANGES) { + System.out.println("Deleting Tag file for " + folder.toOSString()); //$NON-NLS-1$ + } + tagFile.toFile().delete(); + } + } + + // write CVS/Entries.Static + IPath staticFile = cvsSubDir.append(STATIC); + if(info.getIsStatic()) { + // the existance of the file is all that matters + if (Policy.DEBUG_METAFILE_CHANGES) { + System.out.println("Writing Static file for " + folder.toOSString()); //$NON-NLS-1$ + } + writeLines(staticFile, new String[] {""}); //$NON-NLS-1$ + } else { + if(staticFile.toFile().exists()) { + if (Policy.DEBUG_METAFILE_CHANGES) { + System.out.println("Deleting Static file for " + folder.toOSString()); //$NON-NLS-1$ + } + staticFile.toFile().delete(); + } + } + } catch(CoreException e) { + throw CVSException.wrapException(e); + } catch (IOException e) { + throw CVSException.wrapException(e); + } + } + + /** * Returns all .cvsignore entries for the specified folder. */ public static String[] readCVSIgnoreEntries(IContainer folder) throws CVSException { @@ -328,15 +481,34 @@ return null; } - // process Notify file contents String[] entries = readLines(cvsSubDir.getFile(NOTIFY)); + return readAllNotifyInfo(parent.getFullPath(), entries); + } + + /** + * Reads the CVS/Notify file from the specified folder and returns NotifyInfo instances + * for the data stored therein. If the folder does not have a CVS subdirectory then null is returned. + */ + public static NotifyInfo[] readAllNotifyInfo(IPath parent) throws CVSException { + IPath cvsSubDir = getCVSSubdirectory(parent); + + if (!folderExists(cvsSubDir)){ + return null; + } + + String[] entries = readLines(cvsSubDir.append(NOTIFY)); + return readAllNotifyInfo(parent, entries); + } + + private static NotifyInfo[] readAllNotifyInfo(IPath path, String[] entries) throws CVSException { + // process Notify file contents if (entries == null) return null; Map infos = new TreeMap(); for (int i = 0; i < entries.length; i++) { String line = entries[i]; if(!"".equals(line)) { //$NON-NLS-1$ try { - NotifyInfo info = new NotifyInfo(parent, line); + NotifyInfo info = new NotifyInfo(path, line); infos.put(info.getName(), info); } catch (CVSException e) { // We couldn't parse the notify info @@ -383,6 +555,38 @@ } /** + * Writes the CVS/Notify file to the specified folder using the data contained in the + * specified NotifyInfo instances. A CVS subdirectory must already exist (an exception + * is thrown if it doesn't). + */ + public static void writeAllNotifyInfo(IPath parent, NotifyInfo[] infos) throws CVSException { + // get the CVS directory + IPath cvsSubDir = getCVSSubdirectory(parent); + // write lines will throw an exception if the CVS directory does not exist + + if (infos.length == 0) { + IPath notifyFile = cvsSubDir.append(NOTIFY); + if(notifyFile.toFile().exists()) { + notifyFile.toFile().delete(); + } + } else { + // format file contents + String[] entries = new String[infos.length]; + for (int i = 0; i < infos.length; i++) { + NotifyInfo info = infos[i]; + entries[i] = info.getNotifyLine(); + } + + // write Notify entries + try { + writeLines(cvsSubDir.append(NOTIFY), entries); + } catch (IOException e) { + throw CVSException.wrapException(e); + } + } + } + + /** * Reads the CVS/Baserev file from the specified folder and returns * BaserevInfo instances for the data stored therein. If the folder does not * have a CVS subdirectory then null is returned. @@ -396,6 +600,28 @@ // process Notify file contents String[] entries = readLines(cvsSubDir.getFile(BASEREV)); + return readAllBaserevInfo(entries); + } + + /** + * Reads the CVS/Baserev file from the specified folder and returns + * BaserevInfo instances for the data stored therein. If the folder does not + * have a CVS subdirectory then null is returned. + */ + public static BaserevInfo[] readAllBaserevInfo(IPath parent) throws CVSException { + IPath cvsSubDir = getCVSSubdirectory(parent); + + if (!folderExists(cvsSubDir)){ + return null; + } + + // process Notify file contents + String[] entries = readLines(cvsSubDir.append(BASEREV)); + return readAllBaserevInfo(entries); + } + + private static BaserevInfo[] readAllBaserevInfo(String[] entries) + throws CVSException { if (entries == null) return null; Map infos = new TreeMap(); for (int i = 0; i < entries.length; i++) { @@ -419,15 +645,40 @@ IFolder cvsSubDir = getCVSSubdirectory(parent); // write lines will throw an exception if the CVS directory does not exist + String[] entries = formatBaserevInfo(infos); + + // write Notify entries + writeLines(cvsSubDir.getFile(BASEREV), entries); + } + + /** + * Writes the CVS/Baserev file to the specified folder using the data + * contained in the specified BaserevInfo instances. A CVS subdirectory must + * already exist (an exception is thrown if it doesn't). + */ + public static void writeAllBaserevInfo(IPath parent, BaserevInfo[] infos) throws CVSException { + // get the CVS directory + IPath cvsSubDir = getCVSSubdirectory(parent); + // write lines will throw an exception if the CVS directory does not exist + + String[] entries = formatBaserevInfo(infos); + + // write Notify entries + try { + writeLines(cvsSubDir.append(BASEREV), entries); + } catch (IOException e) { + CVSException.wrapException(e); + } + } + + private static String[] formatBaserevInfo(BaserevInfo[] infos) { // format file contents String[] entries = new String[infos.length]; for (int i = 0; i < infos.length; i++) { BaserevInfo info = infos[i]; entries[i] = info.getEntryLine(); } - - // write Notify entries - writeLines(cvsSubDir.getFile(BASEREV), entries); + return entries; } /** @@ -438,6 +689,13 @@ } /** + * Returns the CVS subdirectory for this folder. + */ + private static IPath getCVSSubdirectory(IPath folder) { + return folder.append(CVS_DIRNAME); + } + + /** * Creates and makes team-private and returns a CVS subdirectory in this folder. */ private static IFolder createCVSSubdirectory(IContainer folder) throws CVSException { @@ -471,6 +729,16 @@ } } + /** + * Creates and makes team-private and returns a CVS subdirectory in this folder. + */ + private static IPath createCVSSubdirectory(IPath folder) throws CVSException { + final IPath cvsSubDir = getCVSSubdirectory(folder); + if (! cvsSubDir.toFile().exists()) + cvsSubDir.toFile().mkdir(); + return cvsSubDir; + } + protected static boolean existsInFileSystem(IFolder cvsSubDir) { URI uri = cvsSubDir.getLocationURI(); if (uri != null) { @@ -493,6 +761,35 @@ private static String readFirstLine(IFile file) throws CVSException { try { InputStream in = getInputStream(file); + return readFirstLine(in); + } catch (IOException e) { + throw CVSException.wrapException(e); + } catch (CoreException e) { + // If the IFile doesn't exist or the underlying File doesn't exist, + // just return null to indicate the absence of the file + if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND + || e.getStatus().getCode() == IResourceStatus.FAILED_READ_LOCAL) + return null; + throw CVSException.wrapException(e); + } + } + + /* + * Reads the first line of the specified file. + * Returns null if the file does not exist, or the empty string if it is blank. + */ + private static String readFirstLine(IPath file) throws CVSException { + try { + if (!file.toFile().exists()) + return null; + InputStream in = new FileInputStream(file.toFile()); + return readFirstLine(in); + } catch (IOException e) { + throw CVSException.wrapException(e); + } + } + + private static String readFirstLine(InputStream in) throws IOException { if (in != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(in), 512); try { @@ -504,16 +801,6 @@ } } return null; - } catch (IOException e) { - throw CVSException.wrapException(e); - } catch (CoreException e) { - // If the IFile doesn't exist or the underlying File doesn't exist, - // just return null to indicate the absence of the file - if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND - || e.getStatus().getCode() == IResourceStatus.FAILED_READ_LOCAL) - return null; - throw CVSException.wrapException(e); - } } private static InputStream getInputStream(IFile file) throws CoreException, FileNotFoundException { @@ -547,6 +834,20 @@ private static String[] readLines(IFile file) throws CVSException { try { InputStream in = getInputStream(file); + return readLines(in); + } catch (IOException e) { + throw CVSException.wrapException(e); + } catch (CoreException e) { + // If the IFile doesn't exist or the underlying File doesn't exist, + // just return null to indicate the absence of the file + if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND + || e.getStatus().getCode() == IResourceStatus.FAILED_READ_LOCAL) + return null; + throw CVSException.wrapException(e); + } + } + + private static String[] readLines(InputStream in) throws IOException { if (in != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(in), 512); List fileContentStore = new ArrayList(); @@ -561,14 +862,19 @@ } } return null; - } catch (IOException e) { - throw CVSException.wrapException(e); - } catch (CoreException e) { - // If the IFile doesn't exist or the underlying File doesn't exist, - // just return null to indicate the absence of the file - if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND - || e.getStatus().getCode() == IResourceStatus.FAILED_READ_LOCAL) + } + + /* + * Reads all lines of the specified file. + * Returns null if the file does not exist. + */ + private static String[] readLines(IPath file) throws CVSException { + try { + if (!file.toFile().exists()) return null; + InputStream in = new FileInputStream(file.toFile()); + return readLines(in); + } catch (IOException e) { throw CVSException.wrapException(e); } } @@ -604,6 +910,20 @@ } } + /* + * Writes all lines to the specified file, using linefeed terminators for + * compatibility with other CVS clients. + */ + private static void writeLines(IPath file, final String[] contents) throws IOException, CVSException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + writeLinesToStreamAndClose(os, contents); + File javaFile = file.toFile(); + RandomAccessFile raFile = new RandomAccessFile(javaFile, "rw"); //$NON-NLS-1$ + raFile.write(os.toByteArray()); + raFile.setLength(os.toByteArray().length); + raFile.close(); + } + private static void writeLinesToStreamAndClose(OutputStream os, String[] contents) throws CVSException { byte[] lineEnd = getLineDelimiter(); try { @@ -650,6 +970,45 @@ } } /** + * Method writeFileToBaseDirectory. + * + * @param file + * @param info + */ + public static void writeFileToBaseDirectory(IPath file, IProgressMonitor monitor) throws CVSException { + monitor = Policy.monitorFor(monitor); + monitor.beginTask(null, 100); + try { + IPath baseFolder = getBaseDirectory(file); + if (!baseFolder.toFile().exists()) { + baseFolder.toFile().createNewFile(); + } + IPath target = baseFolder.append(file.lastSegment()); + if (target.toFile().exists()) { + // XXX Should ensure that we haven't already copied it + // XXX write the revision to the CVS/Baserev file + setReadOnly(target, false); + target.toFile().delete(); + } + InputStream in = new FileInputStream(file.toFile()); + OutputStream out = new FileOutputStream(target.toFile()); + + // Transfer bytes from in to out + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + in.close(); + out.close(); + target.toFile().setLastModified(file.toFile().lastModified()); + } catch (IOException e) { + throw CVSException.wrapException(e); + } finally { + monitor.done(); + } + } + /** * Method restoreFileFromBaseDirectory. * @param file * @param info @@ -678,6 +1037,35 @@ monitor.done(); } } + /** + * Method restoreFileFromBaseDirectory. + * @param file + * @param info + * @param monitor + */ + public static void restoreFileFromBaseDirectory(IPath file, IProgressMonitor monitor) throws CVSException { + monitor = Policy.monitorFor(monitor); + monitor.beginTask(null, 100); + try { + IPath baseFolder = getBaseDirectory(file); + IPath source = baseFolder.append(file.lastSegment()); + if (!source.toFile().exists()) { + IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.SyncFileWriter_baseNotAvailable, new String[] { file.toOSString() }), file); + throw new CVSException(status); + } + if (file.toFile().exists()) { + file.toFile().delete(); + } + // Make the source writable to avoid problems on some file systems (bug 109308) + setReadOnly(source, false); + // Copy the file so the timestamp is maintained + source.toFile().renameTo(file.toFile()); + } catch (CoreException e) { + throw CVSException.wrapException(e); + } finally { + monitor.done(); + } + } private static void setReadOnly(IFile source, boolean readOnly) { ResourceAttributes attrs = source.getResourceAttributes(); @@ -692,6 +1080,19 @@ } } + private static void setReadOnly(IPath source, boolean readOnly) { + try { + IFileStore fileStore = EFS.getStore(source.toFile().toURI()); + if (fileStore != null) { + IFileInfo fileInfo = fileStore.fetchInfo(); + if (fileInfo != null) + fileInfo.setAttribute(EFS.ATTRIBUTE_READ_ONLY, readOnly); + } + } catch (CoreException e) { + CVSException.wrapException(e); + } + } + /** * Method deleteFileFromBaseDirectory. * @param file @@ -714,12 +1115,38 @@ } } + /** + * Method deleteFileFromBaseDirectory. + * @param file + * @param monitor + */ + public static void deleteFileFromBaseDirectory(IPath file, IProgressMonitor monitor) throws CVSException { + monitor = Policy.monitorFor(monitor); + monitor.beginTask(null, 100); + try { + IPath baseFolder = getBaseDirectory(file); + IPath source = baseFolder.append(file.lastSegment()); + if (source.toFile().exists()) { + setReadOnly(source, false); + source.toFile().delete(); + } + } finally { + monitor.done(); + } + } + private static IFolder getBaseDirectory(IFile file) { IContainer cvsFolder = getCVSSubdirectory(file.getParent()); IFolder baseFolder = cvsFolder.getFolder(new Path(BASE_DIRNAME)); return baseFolder; } + private static IPath getBaseDirectory(IPath file) { + IPath cvsFolder = getCVSSubdirectory(file.removeLastSegments(1)); + IPath baseFolder = cvsFolder.append(new Path(BASE_DIRNAME)); + return baseFolder; + } + /** * Return a handle to the CVS/Template file for the given folder * @param folder @@ -742,6 +1169,17 @@ return baseFile.exists(); } + /** + * Method isEdited. + * @param resource + * @return boolean + */ + public static boolean isEdited(IPath file) { + IPath baseFolder = getBaseDirectory(file); + IPath baseFile = baseFolder.append(file.lastSegment()); + return baseFile.toFile().exists(); + } + private static byte[] getLineDelimiter() { if (CVSProviderPlugin.getPlugin().isUsePlatformLineend()) { String property = System.getProperty("line.separator"); //$NON-NLS-1$ Index: src/org/eclipse/team/internal/ccvs/core/util/Util.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/Util.java,v retrieving revision 1.50 diff -u -r1.50 Util.java --- src/org/eclipse/team/internal/ccvs/core/util/Util.java 22 May 2007 20:19:57 -0000 1.50 +++ src/org/eclipse/team/internal/ccvs/core/util/Util.java 19 Jul 2010 13:21:40 -0000 @@ -101,6 +101,40 @@ return result; } + /* + * * + * Get the extention of the path of resource + * relative to the path of root + * + * @throws CVSException if root is not a root-folder of resource + */ + public static String getRelativePath(IPath rootName, IPath resourceName) + throws CVSException { + + if (!rootName.isPrefixOf(resourceName)) { + int count = rootName.matchingFirstSegments(resourceName); + if (count == 0) + throw new CVSException(CVSMessages.Util_Internal_error__resource_does_not_start_with_root_3); + resourceName = resourceName.removeFirstSegments(count); + for (int i = 0; i < rootName.segmentCount() - count; i++) + resourceName = new Path("..").append(resourceName); //$NON-NLS-1$ + return resourceName.toPortableString(); + } + + // Otherwise we would get an ArrayOutOfBoundException + // in case of two equal Resources + if (rootName.segmentCount() == resourceName.segmentCount()) { + return ""; //$NON-NLS-1$ + } + + // Remove leading slash if there is one + String result = resourceName.toString().substring(rootName.toString().length()); + if (result.startsWith("/")) { //$NON-NLS-1$ + result = result.substring(1); + } + return result; + } + /** * Append the prefix and suffix to form a valid CVS path. */ #P org.eclipse.team.cvs.ui Index: src/org/eclipse/team/internal/ccvs/ui/CVSLightweightDecorator.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/CVSLightweightDecorator.java,v retrieving revision 1.73 diff -u -r1.73 CVSLightweightDecorator.java --- src/org/eclipse/team/internal/ccvs/ui/CVSLightweightDecorator.java 10 Sep 2007 15:04:24 -0000 1.73 +++ src/org/eclipse/team/internal/ccvs/ui/CVSLightweightDecorator.java 19 Jul 2010 13:21:43 -0000 @@ -276,6 +276,7 @@ ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource); cvsDecoration.setResourceType(resource.getType()); + boolean locationIsNull = resource.getLocation() == null; cvsDecoration.setHasRemote(hasRemote(cvsResource)); if (cvsResource.isIgnored()) { cvsDecoration.setIgnored(true); @@ -304,7 +305,7 @@ if (store.getBoolean(ICVSUIConstants.PREF_SHOW_NEWRESOURCE_DECORATION)) { if (cvsResource.exists()) { if (cvsResource.isFolder()) { - if (!((ICVSFolder) cvsResource).isCVSFolder()) { + if (!((ICVSFolder) cvsResource).isCVSFolder() && !locationIsNull) { cvsDecoration.setNewResource(true); } } else if (!cvsResource.isManaged()) { Index: src/org/eclipse/team/internal/ccvs/ui/actions/AddAction.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/AddAction.java,v retrieving revision 1.30 diff -u -r1.30 AddAction.java --- src/org/eclipse/team/internal/ccvs/ui/actions/AddAction.java 3 Jun 2008 13:35:44 -0000 1.30 +++ src/org/eclipse/team/internal/ccvs/ui/actions/AddAction.java 19 Jul 2010 13:21:43 -0000 @@ -97,9 +97,6 @@ * @see org.eclipse.team.internal.ccvs.ui.actions.WorkspaceAction#isEnabledForCVSResource(org.eclipse.team.internal.ccvs.core.ICVSResource) */ protected boolean isEnabledForCVSResource(ICVSResource cvsResource) throws CVSException { - // Add to version control should never be enabled for linked resources - IResource resource = cvsResource.getIResource(); - if (resource.isLinked()) return false; return super.isEnabledForCVSResource(cvsResource); } Index: src/org/eclipse/team/internal/ccvs/ui/actions/WorkspaceAction.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/actions/WorkspaceAction.java,v retrieving revision 1.34 diff -u -r1.34 WorkspaceAction.java --- src/org/eclipse/team/internal/ccvs/ui/actions/WorkspaceAction.java 9 Jun 2010 16:25:09 -0000 1.34 +++ src/org/eclipse/team/internal/ccvs/ui/actions/WorkspaceAction.java 19 Jul 2010 13:21:43 -0000 @@ -109,6 +109,7 @@ * Recursively check for and handle orphaned CVS folders */ private void handleOrphanedSubtree(final ICVSFolder folder) throws CVSException { + if (folder.getIResource() != null) { if (folder.getIResource().getType() == IResource.PROJECT) return; if (CVSWorkspaceRoot.isOrphanedSubtree((IContainer)folder.getIResource())) { try { @@ -129,6 +130,7 @@ } handleOrphanedSubtree(folder.getParent()); } + } /** * Return true if the sync info is loaded for all selected resources. @@ -311,9 +313,6 @@ if (! resource.isAccessible()) return false; } - // no CVS actions are enabled if the selection contains a linked resource - if (CVSWorkspaceRoot.isLinkedResource(resource)) return false; - // only enable for resources in a project shared with CVS if(RepositoryProvider.getProvider(resource.getProject(), CVSProviderPlugin.getTypeId()) == null) { return false; Index: src/org/eclipse/team/internal/ccvs/ui/operations/AddOperation.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/AddOperation.java,v retrieving revision 1.11 diff -u -r1.11 AddOperation.java --- src/org/eclipse/team/internal/ccvs/ui/operations/AddOperation.java 10 May 2006 17:48:12 -0000 1.11 +++ src/org/eclipse/team/internal/ccvs/ui/operations/AddOperation.java 19 Jul 2010 13:21:43 -0000 @@ -103,7 +103,7 @@ * must be included in the resources array. *

*/ - private void add(CVSTeamProvider provider, IResource[] resources, int depth, IProgressMonitor progress) throws CVSException { + private void add(final CVSTeamProvider provider, IResource[] resources, int depth, final IProgressMonitor progress) throws CVSException { // Visit the children of the resources using the depth in order to // determine which folders, text files and binary files need to be added @@ -121,7 +121,10 @@ // Auto-add parents if they are not already managed IContainer parent = currentResource.getParent(); ICVSResource cvsParentResource = CVSWorkspaceRoot.getCVSResourceFor(parent); - while (parent.getType() != IResource.ROOT && parent.getType() != IResource.PROJECT && ! isManaged(cvsParentResource)) { + while (parent.getType() != IResource.ROOT + && parent.getType() != IResource.PROJECT + && ! isManaged(cvsParentResource) + && cvsParentResource.getLocation() != null) { folders.add(cvsParentResource); parent = parent.getParent(); cvsParentResource = cvsParentResource.getParent(); @@ -135,7 +138,7 @@ ICVSResource mResource = CVSWorkspaceRoot.getCVSResourceFor(resource); // Add the resource is its not already managed and it was either // added explicitly (is equal currentResource) or is not ignored - if (! isManaged(mResource) && (currentResource.equals(resource) || ! mResource.isIgnored())) { + if ((mResource.getLocation() != null) && !isManaged(mResource) && (currentResource.equals(resource) || ! mResource.isIgnored())) { if (resource.getType() == IResource.FILE) { KSubstOption ksubst= getKSubstOption((IFile)resource); Set set = (Set) files.get(ksubst); @@ -172,14 +175,16 @@ progress.beginTask(null, files.size() * 10 + (folders.isEmpty() ? 0 : 10)); try { if (!folders.isEmpty()) { - Session session = new Session(getRemoteLocation(provider), getLocalRoot(provider), true /* output to console */); + LocalRootRunnable runnable = new LocalRootRunnable(provider) { + public void executeSession(ICVSFolder localRoot, ICVSResource resources[], boolean recurse) throws CVSException, InterruptedException { + Session session = new Session(getRemoteLocation(provider), localRoot, true /* output to console */); session.open(Policy.subMonitorFor(progress, 2), true /* open for modification */); try { IStatus status = Command.ADD.execute( session, Command.NO_GLOBAL_OPTIONS, Command.NO_LOCAL_OPTIONS, - (ICVSResource[])folders.toArray(new ICVSResource[folders.size()]), + resources, null, Policy.subMonitorFor(progress, 8)); if (status.getCode() == CVSStatus.SERVER_ERROR) { @@ -189,18 +194,23 @@ session.close(); } } + }; + runnable.run(getLocalRoot(provider), (ICVSResource[]) folders.toArray(new ICVSResource[0]), false); + } for (Iterator it = files.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); final KSubstOption ksubst = (KSubstOption) entry.getKey(); final Set set = (Set) entry.getValue(); - Session session = new Session(getRemoteLocation(provider), getLocalRoot(provider), true /* output to console */); + LocalRootRunnable runnable = new LocalRootRunnable(provider) { + public void executeSession(ICVSFolder localRoot, ICVSResource resources[], boolean recurse) throws CVSException, InterruptedException { + Session session = new Session(getRemoteLocation(provider), localRoot, true /* output to console */); session.open(Policy.subMonitorFor(progress, 2), true /* open for modification */); try { IStatus status = Command.ADD.execute( session, Command.NO_GLOBAL_OPTIONS, new LocalOption[] { ksubst }, - (ICVSResource[])set.toArray(new ICVSResource[set.size()]), + resources, null, Policy.subMonitorFor(progress, 8)); if (status.getCode() == CVSStatus.SERVER_ERROR) { @@ -210,6 +220,11 @@ session.close(); } } + }; + runnable.run(getLocalRoot(provider), (ICVSResource[]) set.toArray(new ICVSResource[0]), false); + } + } catch (InterruptedException e) { + CVSException.wrapException(e); } finally { progress.done(); } Index: src/org/eclipse/team/internal/ccvs/ui/operations/BranchOperation.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/BranchOperation.java,v retrieving revision 1.19 diff -u -r1.19 BranchOperation.java --- src/org/eclipse/team/internal/ccvs/ui/operations/BranchOperation.java 3 Jun 2008 13:35:45 -0000 1.19 +++ src/org/eclipse/team/internal/ccvs/ui/operations/BranchOperation.java 19 Jul 2010 13:21:43 -0000 @@ -130,21 +130,22 @@ } } - private void makeBranch(CVSTeamProvider provider, IResource[] resources, final CVSTag versionTag, final CVSTag branchTag, boolean moveToBranch, boolean recurse, IProgressMonitor monitor) throws TeamException { + private void makeBranch(final CVSTeamProvider provider, IResource[] resources, final CVSTag versionTag, final CVSTag branchTag, boolean moveToBranch, boolean recurse, final IProgressMonitor monitor) throws TeamException { // Determine the total amount of work int totalWork = (versionTag!= null ? 60 : 40) + (moveToBranch ? 20 : 0); monitor.beginTask(CVSUIMessages.CVSTeamProvider_makeBranch, totalWork); try { // Build the arguments list + LocalRootRunnable runnable = new LocalRootRunnable() { + public void executeSession(ICVSFolder localRoot, IResource resources[], boolean recurse) throws CVSException, InterruptedException, CVSServerException { ICVSResource[] arguments = getCVSArguments(resources); LocalOption[] localOptions = getLocalOptions(recurse); - // Tag the remote resources IStatus status = null; if (versionTag != null) { // Version using a custom tag command that skips added but not commited reesources - Session session = new Session(getRemoteLocation(provider), getLocalRoot(provider), true /* output to console */); + Session session = new Session(getRemoteLocation(provider),localRoot, true /* output to console */); session.open(Policy.subMonitorFor(monitor, 5), true /* open for modification */); try { status = Command.CUSTOM_TAG.execute( @@ -160,7 +161,7 @@ } if (status.isOK()) { // Branch using the tag - session = new Session(getRemoteLocation(provider), getLocalRoot(provider), true /* output to console */); + session = new Session(getRemoteLocation(provider), localRoot, true /* output to console */); session.open(Policy.subMonitorFor(monitor, 5), true /* open for modification */); try { status = Command.CUSTOM_TAG.execute( @@ -177,7 +178,7 @@ } } else { // Just branch using tag - Session session = new Session(getRemoteLocation(provider), getLocalRoot(provider), true /* output to console */); + Session session = new Session(getRemoteLocation(provider), localRoot, true /* output to console */); session.open(Policy.subMonitorFor(monitor, 5), true /* open for modification */); try { status = Command.CUSTOM_TAG.execute( @@ -191,17 +192,20 @@ } finally { session.close(); } - } if ( ! status.isOK()) { throw new CVSServerException(status); } - + } + }; + runnable.run(getLocalRoot(provider), resources, recurse); // Set the tag of the local resources to the branch tag (The update command will not // properly update "cvs added" and "cvs removed" resources so a custom visitor is used if (moveToBranch) { setTag(provider, resources, branchTag, recurse, Policy.subMonitorFor(monitor, 20)); } + } catch (InterruptedException e) { + CVSException.wrapException(e); } finally { monitor.done(); } Index: src/org/eclipse/team/internal/ccvs/ui/operations/LocalRootRunnable.java =================================================================== RCS file: src/org/eclipse/team/internal/ccvs/ui/operations/LocalRootRunnable.java diff -N src/org/eclipse/team/internal/ccvs/ui/operations/LocalRootRunnable.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/team/internal/ccvs/ui/operations/LocalRootRunnable.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,395 @@ +/******************************************************************************* + * Copyright (c) 2009 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.*; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.Status; +import org.eclipse.osgi.util.NLS; +import org.eclipse.team.internal.ccvs.core.*; +import org.eclipse.team.internal.ccvs.core.client.*; +import org.eclipse.team.internal.ccvs.core.client.listeners.ICommandOutputListener; +import org.eclipse.team.internal.ccvs.core.connection.CVSServerException; +import org.eclipse.team.internal.ccvs.core.resources.CVSFileSystemRoot; +import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot; +import org.eclipse.team.internal.ccvs.ui.Policy; + +public class LocalRootRunnable { + + private CVSTeamProvider provider; + + public LocalRootRunnable() { + super(); + } + + public LocalRootRunnable(CVSTeamProvider provider) { + super(); + this.provider = provider; + } + + protected void executeSession(ICVSFolder localRoot, IResource resources[], boolean recurse) throws CVSException, InterruptedException, CVSServerException {} + + protected void executeSession(ICVSFolder localRoot, ICVSResource resources[], boolean recurse) throws CVSException, InterruptedException, CVSServerException {} + + protected IStatus executeSessionWithStatus(ICVSFolder localRoot, IResource resources[], boolean recurse) throws CVSException, InterruptedException, CVSServerException {return Status.OK_STATUS;} + + protected IStatus executeSessionWithStatus(ICVSFolder localRoot, ICVSResource resources[], boolean recurse) throws CVSException, InterruptedException, CVSServerException {return Status.OK_STATUS;} + + private static class Root + { + public Root(ICVSFolder localRoot, List resources, boolean recurse) { + super(); + this.localRoot = localRoot; + this.resources = resources; + this.recurse = recurse; + } + ICVSFolder localRoot; + List resources; // either IResource or ICVSResource + boolean recurse; + + public boolean contains(Root newRoot) { + if (localRoot.getClass().equals(newRoot.localRoot.getClass()) && (recurse == newRoot.recurse)) + return localRoot.getLocation().isPrefixOf(newRoot.localRoot.getLocation()); + return false; + } + + public void merge(Root root) { + ArrayList newList = new ArrayList(resources); + resources = newList; + resources.addAll(root.resources); + } + } + + /* + * Represents either a IResource or ICVSResource + */ + private static class CommonResource { + IResource workspaceResource = null; + ICVSResource cvsResource = null; + + public CommonResource(ICVSResource resource) { + cvsResource = resource; + } + + public CommonResource(IResource resource) { + workspaceResource = resource; + } + + public String toString() { + if (cvsResource != null) + return cvsResource.toString(); + return workspaceResource.toString(); + } + + public boolean isFolder() { + if (cvsResource != null) + return cvsResource.isFolder(); + return workspaceResource instanceof IContainer; + } + + public IPath getLocation() { + if (cvsResource != null) + return cvsResource.getLocation(); + return workspaceResource.getLocation(); + } + + public CommonResource[] members() throws CoreException { + if (cvsResource != null) { + ICVSResource[] resources = ((ICVSFolder) cvsResource).members(ICVSFolder.FILE_MEMBERS | ICVSFolder.FOLDER_MEMBERS); + CommonResource[] result = new CommonResource[resources.length]; + for (int i = 0; i < result.length; i++) + result[i] = new CommonResource(resources[i]); + return result; + } + IResource[] resources = ((IContainer) workspaceResource).members(); + CommonResource[] result = new CommonResource[resources.length]; + for (int i = 0; i < result.length; i++) + result[i] = new CommonResource(resources[i]); + return result; + } + + public ICVSResource getCVSResource() { + if (cvsResource != null) + return cvsResource; + return CVSWorkspaceRoot.getCVSResourceFor(workspaceResource); + } + } + + /* + * Returns the root that is sufficient to accommodate all resources to be committed. + * For example, if the local root's location is c:\foo\bar\, but the resource's location + * is c:\foo\other\res.txt, the common root will be 'c:\foo' + */ + private Root[] getCommonRoots(ICVSFolder localRoot, + IResource[] resources, boolean recurse) throws CVSException { + ArrayList/**/ list = new ArrayList(); + getCommonRoots(list, localRoot, toCommonRootArray(resources), recurse); + return (Root[]) list.toArray(new Root[0]); + } + + private CommonResource[] toCommonRootArray(IResource[] resources) { + CommonResource[] result = new CommonResource[resources.length]; + for (int i = 0;i < resources.length; i++) + result[i] = new CommonResource(resources[i]); + return result; + } + + private Root[] getCommonRoots(ICVSFolder localRoot, + ICVSResource[] resources, boolean recurse) throws CVSException { + ArrayList/**/ list = new ArrayList(); + getCommonRoots(list, localRoot, toCommonRootArray(resources), recurse); + return (Root[]) list.toArray(new Root[0]); + } + + private CommonResource[] toCommonRootArray(ICVSResource[] resources) { + CommonResource[] result = new CommonResource[resources.length]; + for (int i = 0;i < resources.length; i++) + result[i] = new CommonResource(resources[i]); + return result; + } + + private ICVSFolder selectAppropriateRoot(ICVSResource cvsResource) + throws CVSException { + ICVSFolder newRoot = null; + IPath location = cvsResource.getLocation(); + if (!cvsResource.isFolder()) + newRoot = cvsResource.getParent(); + else + newRoot = (ICVSFolder) cvsResource; + while (newRoot != null && !newRoot.isCVSFolder() && newRoot.getLocation().isPrefixOf(location)) { + newRoot = newRoot.getParent(); + } + if ( newRoot != null && (!newRoot.isCVSFolder() || !newRoot.getLocation().isPrefixOf(location))) + newRoot = null; + return newRoot; + } + + private boolean isCVSResource(ICVSResource cvsResource) throws CVSException { + ICVSFolder root = null; + if (!cvsResource.isFolder()) + root = cvsResource.getParent(); + else + root = (ICVSFolder) cvsResource; + + while (root != null && !root.isCVSFolder() && root.getLocation().isPrefixOf(cvsResource.getLocation())) { + root = root.getParent(); + } + + return !(root == null); + } + + /* + * Returns true if the root was changed + */ + private boolean getCommonRoots(ArrayList list, ICVSFolder localRoot, + CommonResource[] resources, boolean recurse) throws CVSException { + boolean wasChanged = false; + IPath rootLocation = localRoot.getLocation(); + if (rootLocation != null) { + ArrayList/**/ notAdded = new ArrayList(); + Vector notCVS = new Vector(); + for (int i = 0; i < resources.length; i++) { + boolean added = false; + boolean isCVS = true; + IPath location = resources[i].getLocation(); + if (location != null) { + if (!rootLocation.isPrefixOf(location)) { + // first, try to get the root as the resource itself, and its immediate workspace parents. + CommonResource cvsResource = resources[i]; + if (isCVSResource(cvsResource.getCVSResource())) { + ICVSFolder newRoot = null; + newRoot = selectAppropriateRoot(cvsResource.getCVSResource()); + + if (newRoot == null) { + // create FileSystem Parent Resources. + cvsResource = new CommonResource(CVSFileSystemRoot.getCVSResourceFor(cvsResource.getLocation(), localRoot.getProvider())); + newRoot = selectAppropriateRoot(cvsResource.getCVSResource()); + } + if (newRoot == null) + throw new CVSException(new CVSStatus(IStatus.ERROR, + CVSMessages.CVSRepositoryLocation_mixedRepository)); + added = true; + wasChanged = true; + getCommonChildrenRoots(list, new CommonResource[] {resources[i]}, recurse, newRoot); + } else { + notCVS.add(resources[i]); + isCVS = false; + } + } + } + if (!added && isCVS) + notAdded.add(resources[i]); + } + if (!notAdded.isEmpty()) + wasChanged |= getCommonChildrenRoots(list, (CommonResource[]) notAdded.toArray(new CommonResource[0]), recurse, localRoot); + + // write to log the files that are located somewhere not under cvs root + if (!notCVS.isEmpty()) { + if (provider != null) { + Session session = new Session(provider.getCVSWorkspaceRoot().getRemoteLocation(), localRoot, true); + session.open(Policy.subMonitorFor(null, 2), true /* open for modification */); + try { + ICommandOutputListener listener = new CommandOutputListener(); + for (int i = 0; i < notCVS.size(); i++){ + CommonResource cvsResource = (CommonResource) notCVS.get(i); + String argument = NLS.bind(CVSMessages.Resource_not_in_CVS, + new String[] {cvsResource.getCVSResource().getRelativePath(localRoot), + cvsResource.getLocation().toString()}); + IStatus status = listener.messageLine(argument, session.getCVSRepositoryLocation(), session.getLocalRoot(), null); + session.addError(status); // The session ignores OK status + ConsoleListeners.getInstance().messageLineReceived(session, argument, status); + } + } finally { + session.close(); + } + } + } + } + else + wasChanged = getCommonChildrenRoots(list, resources, recurse, localRoot); + return wasChanged; + } + + private boolean getCommonChildrenRoots(ArrayList list, + CommonResource[] resources, boolean recurse, ICVSFolder newRoot) + throws CVSException { + boolean wasChanged = false; + if (recurse) { + boolean hadFolderChildren = false; + ArrayList/**/ notAdded = new ArrayList(); + for (int i = 0; i < resources.length; i++) { + CommonResource resource = resources[i]; + if (resource.isFolder()) { + hadFolderChildren = true; + try { + CommonResource[] resChildren = null; + try { + resChildren = resource.members(); + } catch (CoreException e) { + addToList(list, new Root(newRoot, Arrays.asList(new CommonResource[] {resource}), true)); + } + if (resChildren != null) { + ArrayList childrenList = new ArrayList(); + boolean changed = getCommonRoots(childrenList, newRoot, resChildren, true); + if (changed) { + list.addAll(childrenList); + recurse = false; + wasChanged = true; + } + else { + if (!newRoot.getLocation().isPrefixOf(resource.getLocation())) { + ICVSFolder localRoot = selectAppropriateRoot(newRoot); + addToList(list, new Root(localRoot, Arrays.asList(new CommonResource[] {resource}), true)); + } + else + notAdded.add(resource); + } + } + } catch (CoreException e) { + throw CVSException.wrapException(e); + } + } + else + notAdded.add(resource); + } + if (!notAdded.isEmpty()) + addToList(list, new Root(newRoot, notAdded, recurse && hadFolderChildren)); + } + else + addToList(list, new Root(newRoot, Arrays.asList(resources), recurse)); + return wasChanged; + } + + private void addToList(ArrayList list, Root newRoot) { + Iterator it = list.iterator(); + while (it.hasNext()) { + Root root = (Root) it.next(); + if (root.contains(newRoot)) { + root.merge(newRoot); + return; + } + if (newRoot.contains(root)) { + newRoot.merge(root); + list.remove(root); + list.add(newRoot); + return; + } + } + list.add(newRoot); + } + + public void run(ICVSFolder localRoot, IResource[] resources, + boolean recurse) throws CVSException, InterruptedException, CVSServerException { + Root commonRoots[] = getCommonRoots(localRoot, resources, recurse); + for (int i = 0; i < commonRoots.length; i++) { + IResource [] array = toIResourceArray(commonRoots[i].resources); + executeSession(commonRoots[i].localRoot, array, commonRoots[i].recurse); + } + } + + public IStatus runWithStatus(ICVSFolder localRoot, IResource[] resources, + boolean recurse) throws CVSException, InterruptedException, CVSServerException { + Root commonRoots[] = getCommonRoots(localRoot, resources, recurse); + for (int i = 0; i < commonRoots.length; i++) { + IResource [] array = toIResourceArray(commonRoots[i].resources); + IStatus status = executeSessionWithStatus(commonRoots[i].localRoot, array, commonRoots[i].recurse); + if (!status.isOK()) + return status; + } + return Status.OK_STATUS; + } + + private IResource[] toIResourceArray(List resources) { + IResource[] result = new IResource[resources.size()]; + Iterator it = resources.iterator(); + int i = 0; + while (it.hasNext()) { + CommonResource commonRes = (CommonResource) it.next(); + result[i++] = commonRes.workspaceResource; + } + return result; + } + + public void run(ICVSFolder localRoot, ICVSResource[] resources, + boolean recurse) throws CVSException, InterruptedException, CVSServerException { + Root commonRoots[] = getCommonRoots(localRoot, resources, recurse); + for (int i = 0; i < commonRoots.length; i++) { + ICVSResource [] array = toICVSResourceArray(commonRoots[i].resources); + executeSession(commonRoots[i].localRoot, array, commonRoots[i].recurse); + } + } + + public IStatus runWithStatus(ICVSFolder localRoot, ICVSResource[] resources, + boolean recurse) throws CVSException, InterruptedException, CVSServerException { + Root commonRoots[] = getCommonRoots(localRoot, resources, recurse); + for (int i = 0; i < commonRoots.length; i++) { + ICVSResource [] array = toICVSResourceArray(commonRoots[i].resources); + IStatus status = executeSessionWithStatus(commonRoots[i].localRoot, array, commonRoots[i].recurse); + if (!status.isOK()) + return status; + } + return Status.OK_STATUS; + } + + private ICVSResource[] toICVSResourceArray(List resources) { + ICVSResource[] result = new ICVSResource[resources.size()]; + Iterator it = resources.iterator(); + int i = 0; + while (it.hasNext()) { + CommonResource commonRes = (CommonResource) it.next(); + result[i++] = commonRes.cvsResource; + } + return result; + } +} Index: src/org/eclipse/team/internal/ccvs/ui/operations/RepositoryProviderOperation.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/RepositoryProviderOperation.java,v retrieving revision 1.40 diff -u -r1.40 RepositoryProviderOperation.java --- src/org/eclipse/team/internal/ccvs/ui/operations/RepositoryProviderOperation.java 24 May 2010 13:43:28 -0000 1.40 +++ src/org/eclipse/team/internal/ccvs/ui/operations/RepositoryProviderOperation.java 19 Jul 2010 13:21:45 -0000 @@ -27,7 +27,6 @@ import org.eclipse.team.core.mapping.provider.SynchronizationScopeManager; import org.eclipse.team.internal.ccvs.core.*; 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.resources.CVSWorkspaceRoot; import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin; @@ -387,15 +386,12 @@ /* * Get the arguments to be passed to a commit or update */ - protected String[] getStringArguments(IResource[] resources) throws CVSException { + protected String[] getStringArguments(ICVSFolder localRoot, IResource[] resources) throws CVSException { List arguments = new ArrayList(resources.length); for (int i=0;i 0)); + + boolean found = false; + for (int i = 0; i < entries.length; i++) { + CVSTag[] tags = entries[0].getTags(); + for (int j = 0; j < tags.length; j++) { + if (tags[j].getName().equals(version)) + found = true; + } + } + assertTrue("file is tagged", found); + } } +