package org.tmatesoft.svn.core.internal.wc16; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.Map; import java.util.logging.Level; import org.tmatesoft.svn.core.SVNCancelException; import org.tmatesoft.svn.core.SVNCommitInfo; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNDirEntry; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNLock; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.SVNRevisionProperty; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.util.SVNDate; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNHashSet; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.util.SVNURLUtil; import org.tmatesoft.svn.core.internal.wc.IOExceptionWrapper; import org.tmatesoft.svn.core.internal.wc.ISVNFileContentFetcher; import org.tmatesoft.svn.core.internal.wc.SVNAdminUtil; import org.tmatesoft.svn.core.internal.wc.SVNCancellableOutputStream; import org.tmatesoft.svn.core.internal.wc.SVNCommitUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNEventFactory; import org.tmatesoft.svn.core.internal.wc.SVNExternal; import org.tmatesoft.svn.core.internal.wc.SVNFileListUtil; import org.tmatesoft.svn.core.internal.wc.SVNFileType; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc.SVNPropertiesManager; import org.tmatesoft.svn.core.internal.wc.SVNStatusEditor; import org.tmatesoft.svn.core.internal.wc.SVNTreeConflictUtil; import org.tmatesoft.svn.core.internal.wc.SVNWCManager; import org.tmatesoft.svn.core.internal.wc.admin.ISVNEntryHandler; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaInfo; import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry; import org.tmatesoft.svn.core.internal.wc.admin.SVNLog; import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator; import org.tmatesoft.svn.core.internal.wc.admin.SVNVersionedProperties; import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess; import org.tmatesoft.svn.core.io.ISVNEditor; import org.tmatesoft.svn.core.io.ISVNLockHandler; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.DefaultSVNCommitHandler; import org.tmatesoft.svn.core.wc.ISVNAddParameters; import org.tmatesoft.svn.core.wc.ISVNCommitHandler; import org.tmatesoft.svn.core.wc.ISVNEventHandler; import org.tmatesoft.svn.core.wc.ISVNInfoHandler; import org.tmatesoft.svn.core.wc.ISVNOptions; import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; import org.tmatesoft.svn.core.wc.ISVNPropertyValueProvider; import org.tmatesoft.svn.core.wc.ISVNRepositoryPool; import org.tmatesoft.svn.core.wc.ISVNStatusHandler; import org.tmatesoft.svn.core.wc.SVNBasicClient; import org.tmatesoft.svn.core.wc.SVNCommitItem; import org.tmatesoft.svn.core.wc.SVNConflictChoice; import org.tmatesoft.svn.core.wc.SVNEvent; import org.tmatesoft.svn.core.wc.SVNEventAction; import org.tmatesoft.svn.core.wc.SVNInfo; import org.tmatesoft.svn.core.wc.SVNPropertyData; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatus; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription; import org.tmatesoft.svn.core.wc.SVNWCUtil; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; /** * The <b>SVNWCClient</b> class combines a number of version control operations * mainly intended for local work with Working Copy items. This class includes * those operations that are destined only for local work on a Working Copy as * well as those that are moreover able to access a repository. * <p/> * <p/> * Here's a list of the <b>SVNWCClient</b>'s methods matched against * corresponing commands of the SVN command line client: * <p/> * <table cellpadding="3" cellspacing="1" border="0" width="70%" bgcolor="#999933"> * <tr bgcolor="#ADB8D9" align="left"> * <td><b>SVNKit</b></td> * <td><b>Subversion</b></td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doAdd()</td> * <td>'svn add'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doGetFileContents()</td> * <td>'svn cat'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doDelete()</td> * <td>'svn delete'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doCleanup()</td> * <td>'svn cleanup'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doInfo()</td> * <td>'svn info'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doLock()</td> * <td>'svn lock'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doUnlock()</td> * <td>'svn unlock'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td> * doSetProperty()</td> * <td> *'svn propset PROPNAME PROPVAL PATH'<br /> *'svn propdel PROPNAME PATH'<br /> *'svn propedit PROPNAME PATH'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doSetRevisionProperty()</td> * <td> *'svn propset PROPNAME --revprop -r REV PROPVAL [URL]'<br /> *'svn propdel PROPNAME --revprop -r REV [URL]'<br /> *'svn propedit PROPNAME --revprop -r REV [URL]'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td> * doGetProperty()</td> * <td> *'svn propget PROPNAME PATH'<br /> *'svn proplist PATH'</td> * <tr bgcolor="#EAEAEA" align="left"> * <td>doGetRevisionProperty()</td> * <td> *'svn propget PROPNAME --revprop -r REV [URL]'<br /> *'svn proplist --revprop -r REV [URL]'</td> * </tr> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doResolve()</td> * <td>'svn resolved'</td> * </tr> * <tr bgcolor="#EAEAEA" align="left"> * <td>doRevert()</td> * <td>'svn revert'</td> * </tr> * </table> * * @version 1.3 * @author TMate Software Ltd. * @since 1.2 * @see <a target="_top" href="http://svnkit.com/kb/examples/">Examples</a> */ public class SVNWCClient16 extends SVNBasicDelegate { private ISVNAddParameters myAddParameters; private ISVNCommitHandler myCommitHandler; private boolean myIsRevertMissingDirectories; /** * Constructs and initializes an <b>SVNWCClient</b> object with the * specified run-time configuration and authentication drivers. * <p/> * <p/> * If <code>options</code> is <span class="javakeyword">null</span>, then * this <b>SVNWCClient</b> will be using a default run-time configuration * driver which takes client-side settings from the default SVN's run-time * configuration area but is not able to change those settings (read more on * {@link ISVNOptions} and {@link SVNWCUtil}). * <p/> * <p/> * If <code>authManager</code> is <span class="javakeyword">null</span>, * then this <b>SVNWCClient</b> will be using a default authentication and * network layers driver (see * {@link SVNWCUtil#createDefaultAuthenticationManager()}) which uses * server-side settings and auth storage from the default SVN's run-time * configuration area (or system properties if that area is not found). * * @param authManager * an authentication and network layers driver * @param options * a run-time configuration options driver */ public SVNWCClient16(ISVNAuthenticationManager authManager, ISVNOptions options) { super(authManager, options); } /** * Constructs and initializes an <b>SVNWCClient</b> object with the * specified run-time configuration and repository pool object. * <p/> * <p/> * If <code>options</code> is <span class="javakeyword">null</span>, then * this <b>SVNWCClient</b> will be using a default run-time configuration * driver which takes client-side settings from the default SVN's run-time * configuration area but is not able to change those settings (read more on * {@link ISVNOptions} and {@link SVNWCUtil}). * <p/> * <p/> * If <code>repositoryPool</code> is <span class="javakeyword">null</span>, * then {@link org.tmatesoft.svn.core.io.SVNRepositoryFactory} will be used * to create {@link SVNRepository repository access objects}. * * @param repositoryPool * a repository pool object * @param options * a run-time configuration options driver */ public SVNWCClient16(ISVNRepositoryPool repositoryPool, ISVNOptions options) { super(repositoryPool, options); } /** * Sets custom add parameters to this client object. * * @param addParameters * extra parameters for add operations * @since 1.2 */ public void setAddParameters(ISVNAddParameters addParameters) { this.myAddParameters = addParameters; } /** * Returns the specified commit handler (if set) being in use or a default * one (<b>DefaultSVNCommitHandler</b>) if no special implementations of * <b>ISVNCommitHandler</b> were previousely provided. * * @return the commit handler being in use or a default one * @see #setCommitHandler(ISVNCommitHandler) * @see DefaultSVNCommitHandler */ public ISVNCommitHandler getCommitHandler() { return this.myCommitHandler; } /** * Sets an implementation of <b>ISVNCommitHandler</b> to the commit handler * that will be used during commit operations to handle commit log messages. * The handler will receive a clien's log message and items (represented as * <b>SVNCommitItem</b> objects) that will be committed. Depending on * implementor's aims the initial log message can be modified (or something * else) and returned back. * <p/> * <p/> * If using <b>SVNWCClient</b> without specifying any commit handler then a * default one will be used - {@link DefaultSVNCommitHandler}. * * @param handler * an implementor's handler that will be used to handle commit * log messages * @see #getCommitHandler() * @see ISVNCommitHandler */ public void setCommitHandler(ISVNCommitHandler handler) { this.myCommitHandler = handler; } /** * Returns the add parameters object used by this object. If no custom * object was specified through a call to * {@link #setAddParameters(ISVNAddParameters)} then * {@link #DEFAULT_ADD_PARAMETERS} is returned. * * @return add parameters object * @since 1.2 */ protected ISVNAddParameters getAddParameters() { return this.myAddParameters; } public void setRevertMissingDirectories(boolean revertMissing) { myIsRevertMissingDirectories = revertMissing; } public boolean isRevertMissingDirectories() { return myIsRevertMissingDirectories; } /** * Outputs the content of file identified by <code>path</code> and * <code>revision</code> to the stream <code>dst</code>. The actual node * revision selected is determined by the path as it exists in * <code>pegRevision</code>. If <code>pegRevision</code> is * {@link SVNRevision#UNDEFINED}, then it defaults to * {@link SVNRevision#WORKING}. * <p/> * If <code>revision</code> is one of: * <ul> * <li>{@link SVNRevision#BASE} * <li>{@link SVNRevision#WORKING} * <li>{@link SVNRevision#COMMITTED} * </ul> * then the file contents are taken from the working copy file item (no * network connection is needed). Otherwise the file item's contents are * taken from the repository at a particular revision. * * @param path * working copy path * @param pegRevision * revision in which the file item is first looked up * @param revision * target revision * @param expandKeywords * if <span class="javakeyword">true</span> then all keywords * presenting in the file and listed in the file's * {@link org.tmatesoft.svn.core.SVNProperty#KEYWORDS}property * (if set) will be substituted, otherwise not * @param dst * the destination where the file contents will be written to * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> refers to a directory <li><code>path * </code> does not exist <li><code>path</code> is not under * version control * </ul> * @see #doGetFileContents(SVNURL,SVNRevision,SVNRevision,boolean,OutputStream) */ public void doGetFileContents(File path, SVNRevision pegRevision, SVNRevision revision, boolean expandKeywords, OutputStream dst) throws SVNException { if (dst == null) { return; } if (revision == null || !revision.isValid()) { revision = SVNRevision.BASE; } else if (revision == SVNRevision.COMMITTED) { revision = SVNRevision.BASE; } if ((!pegRevision.isValid() || pegRevision == SVNRevision.BASE || pegRevision == SVNRevision.WORKING) && (!revision.isValid() || revision == SVNRevision.BASE || revision == SVNRevision.WORKING)) { doGetLocalFileContents(path, dst, revision, expandKeywords); } else { SVNRepository repos = createRepository(null, path, null, pegRevision, revision, null); checkCancelled(); long revNumber = getRevisionNumber(revision, repos, path); SVNNodeKind kind = repos.checkPath("", revNumber); if (kind == SVNNodeKind.DIR) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_IS_DIRECTORY, "URL ''{0}'' refers to a directory", repos.getLocation()); SVNErrorManager.error(err, SVNLogType.WC); } checkCancelled(); if (!expandKeywords) { repos.getFile("", revNumber, null, new SVNCancellableOutputStream(dst, this)); } else { SVNProperties properties = new SVNProperties(); repos.getFile("", revNumber, properties, null); checkCancelled(); String keywords = properties.getStringValue(SVNProperty.KEYWORDS); String eol = properties.getStringValue(SVNProperty.EOL_STYLE); String mimeType = properties.getStringValue(SVNProperty.MIME_TYPE); String charset = SVNTranslator.getCharset(properties.getStringValue(SVNProperty.CHARSET), mimeType, path.getPath(), getOptions()); if (keywords != null || eol != null || charset != null) { String cmtRev = properties.getStringValue(SVNProperty.COMMITTED_REVISION); String cmtDate = properties.getStringValue(SVNProperty.COMMITTED_DATE); String author = properties.getStringValue(SVNProperty.LAST_AUTHOR); Map keywordsMap = SVNTranslator.computeKeywords(keywords, expandKeywords ? repos.getLocation().toString() : null, repos.getRepositoryRoot(true).toString(), author, cmtDate, cmtRev, getOptions()); OutputStream translatingStream = SVNTranslator.getTranslatingOutputStream(dst, charset, SVNTranslator.getEOL(eol, getOptions()), false, keywordsMap, expandKeywords); repos.getFile("", revNumber, null, new SVNCancellableOutputStream(translatingStream, getEventDispatcher())); try { translatingStream.flush(); } catch (IOExceptionWrapper ioew) { throw ioew.getOriginalException(); } catch (IOException e) { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage()), SVNLogType.WC); } } else { repos.getFile("", revNumber, null, new SVNCancellableOutputStream(dst, getEventDispatcher())); } } try { dst.flush(); } catch (IOException e) { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage()), SVNLogType.WC); } } } /** * Outputs the content of file identified by <code>url</code> and * <code>revision</code> to the stream <code>dst</code>. The actual node * revision selected is determined by the path as it exists in * <code>pegRevision</code>. If <code>pegRevision</code> is * {@link SVNRevision#UNDEFINED}, then it defaults to * {@link SVNRevision#HEAD}. * * @param url * a file item's repository location * @param pegRevision * a revision in which the file item is first looked up * @param revision * a target revision * @param expandKeywords * if <span class="javakeyword">true</span> then all keywords * presenting in the file and listed in the file's * {@link org.tmatesoft.svn.core.SVNProperty#KEYWORDS}property * (if set) will be substituted, otherwise not * @param dst * the destination where the file contents will be written to * @throws SVNException * if one of the following is true: * <ul> * <li><code>url</code> refers to a directory * <li>it's impossible to create temporary files ( * {@link java.io.File#createTempFile(java.lang.String,java.lang.String) * createTempFile()}fails) necessary for file translating * </ul> * @see #doGetFileContents(File,SVNRevision,SVNRevision,boolean,OutputStream) */ public void doGetFileContents(SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean expandKeywords, OutputStream dst) throws SVNException { revision = revision == null || !revision.isValid() ? SVNRevision.HEAD : revision; SVNRepository repos = createRepository(url, null, null, pegRevision, revision, null); checkCancelled(); long revNumber = getRevisionNumber(revision, repos, null); checkCancelled(); SVNNodeKind nodeKind = repos.checkPath("", revNumber); checkCancelled(); if (nodeKind == SVNNodeKind.DIR) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_IS_DIRECTORY, "URL ''{0}'' refers to a directory", url); SVNErrorManager.error(err, SVNLogType.WC); } checkCancelled(); if (!expandKeywords) { repos.getFile("", revNumber, null, new SVNCancellableOutputStream(dst, this)); } else { SVNProperties properties = new SVNProperties(); repos.getFile("", revNumber, properties, null); checkCancelled(); String mimeType = properties.getStringValue(SVNProperty.MIME_TYPE); String charset = SVNTranslator.getCharset(properties.getStringValue(SVNProperty.CHARSET), mimeType, repos.getLocation().toString(), getOptions()); String keywords = properties.getStringValue(SVNProperty.KEYWORDS); String eol = properties.getStringValue(SVNProperty.EOL_STYLE); if (charset != null || keywords != null || eol != null) { String cmtRev = properties.getStringValue(SVNProperty.COMMITTED_REVISION); String cmtDate = properties.getStringValue(SVNProperty.COMMITTED_DATE); String author = properties.getStringValue(SVNProperty.LAST_AUTHOR); Map keywordsMap = SVNTranslator.computeKeywords(keywords, expandKeywords ? repos.getLocation().toString() : null, repos.getRepositoryRoot(true).toString(), author, cmtDate, cmtRev, getOptions()); OutputStream translatingStream = SVNTranslator.getTranslatingOutputStream(dst, charset, SVNTranslator.getEOL(eol, getOptions()), false, keywordsMap, expandKeywords); repos.getFile("", revNumber, null, new SVNCancellableOutputStream(translatingStream, getEventDispatcher())); try { translatingStream.flush(); } catch (IOExceptionWrapper ioew) { throw ioew.getOriginalException(); } catch (IOException e) { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage()), SVNLogType.WC); } } else { repos.getFile("", revNumber, null, new SVNCancellableOutputStream(dst, getEventDispatcher())); } } try { dst.flush(); } catch (IOException e) { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage()), SVNLogType.WC); } } /** * Cleans up a working copy. This method is equivalent to a call to * <code>doCleanup(path, false)</code>. * * @param path * a WC path to start a cleanup from * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> does not exist * <li><code>path</code>'s parent directory is not under version * control * </ul> * @see #doCleanup(File,boolean) */ public void doCleanup(File path) throws SVNException { doCleanup(path, false); } /** * Recursively cleans up the working copy, removing locks and resuming * unfinished operations. * <p/> * If you ever get a "working copy locked" error, use this method to remove * stale locks and get your working copy into a usable state again. * <p> * This method operates only on working copies and does not open any network * connection. * * @param path * a WC path to start a cleanup from * @param deleteWCProperties * if <span class="javakeyword">true</span>, removes DAV specific * <span class="javastring">"svn:wc:"</span> properties from the * working copy * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> does not exist <li><code>path</code>'s * parent directory is not under version control * </ul> */ public void doCleanup(File path, boolean deleteWCProperties) throws SVNException { SVNFileType fType = SVNFileType.getType(path); if (fType == SVNFileType.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "''{0}'' does not exist", path); SVNErrorManager.error(err, SVNLogType.WC); } else if (fType == SVNFileType.FILE) { path = path.getParentFile(); } else if (fType == SVNFileType.SYMLINK) { path = SVNFileUtil.resolveSymlink(path); if (SVNFileType.getType(path) == SVNFileType.FILE) { path = path.getParentFile(); } } SVNWCAccess wcAccess = createWCAccess(); try { SVNAdminArea adminArea = wcAccess.open(path, true, true, 0); adminArea.cleanup(); if (deleteWCProperties) { SVNPropertiesManager.deleteWCProperties(adminArea, null, true); } } catch (SVNException e) { if (e instanceof SVNCancelException) { throw e; } else if (!SVNAdminArea.isSafeCleanup()) { throw e; } SVNDebugLog.getDefaultLog().logFine(SVNLogType.WC, "CLEANUP FAILED for " + path); SVNDebugLog.getDefaultLog().logFine(SVNLogType.WC, e); } finally { wcAccess.close(); sleepForTimeStamp(); } } /** * Sets <code>propName</code> to <code>propValue</code> on <code>path</code> * . A <code>propValue</code> of <span class="javakeyword">null</span> will * delete the property. * <p/> * If <code>depth</code> is {@link org.tmatesoft.svn.core.SVNDepth#EMPTY}, * set the property on <code>path</code> only; if {@link SVNDepth#FILES}, * set it on <code>path</code> and its file children (if any); if * {@link SVNDepth#IMMEDIATES}, on <code>path</code> and all of its * immediate children (both files and directories); if * {@link SVNDepth#INFINITY}, on <code>path</code> and everything beneath * it. * <p/> * If <code>propName</code> is an svn-controlled property (i.e. prefixed * with <span class="javastring">"svn:"</span>), then the caller is * responsible for ensuring that the value uses LF line-endings. * <p/> * If <code>skipChecks</code> is <span class="javakeyword">true</span>, this * method does no validity checking. But if <code>skipChecks</code> is <span * class="javakeyword">false</span>, and <code>propName</code> is not a * valid property for <code>path</code>, it throws an exception, either with * an error code {@link org.tmatesoft.svn.core.SVNErrorCode#ILLEGAL_TARGET} * (if the property is not appropriate for <code>path</code>), or with * {@link org.tmatesoft.svn.core.SVNErrorCode#BAD_MIME_TYPE} (if * <code>propName</code> is <span class="javastring">"svn:mime-type"</span>, * but <code>propVal</code> is not a valid mime-type). * <p/> * <code>changeLists</code> is a collection of <code>String</code> * changelist names, used as a restrictive filter on items whose properties * are set; that is, don't set properties on any item unless it's a member * of one of those changelists. If <code>changelists</code> is empty (or * <span class="javakeyword">null</span>), no changelist filtering occurs. * <p> * This method operates only on working copies and does not open any network * connection. * * @param path * working copy path * @param propName * property name * @param propValue * property value * @param skipChecks * <span class="javakeyword">true</span> to force the operation * to run without validity checking * @param depth * working copy tree depth to process * @param handler * a caller's property handler * @param changeLists * changelist names * @throws SVNException * <ul> * <li><code>path</code> does not exist <li>exception with * {@link SVNErrorCode#CLIENT_PROPERTY_NAME} error code - if * <code>propName</code> is a revision property name or not a * valid property name or not a regular property name (one * starting with a <span class="javastring">"svn:entry"</span> * or <span class="javastring">"svn:wc"</span> prefix) * </ul> * @see #doSetProperty(SVNURL,String,SVNPropertyValue,SVNRevision,String,SVNProperties,boolean,ISVNPropertyHandler) * @since 1.2, SVN 1.5 */ public void doSetProperty(File path, String propName, SVNPropertyValue propValue, boolean skipChecks, SVNDepth depth, ISVNPropertyHandler handler, Collection changeLists) throws SVNException { depth = depth == null ? SVNDepth.UNKNOWN : depth; int admLockLevel = SVNWCAccess.INFINITE_DEPTH; if (depth == SVNDepth.EMPTY || depth == SVNDepth.FILES) { admLockLevel = 0; } if (propValue != null && !SVNPropertiesManager.isValidPropertyName(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Bad property name ''{0}''", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNRevisionProperty.isRevisionProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Revision property ''{0}'' not allowed in this context", propName); SVNErrorManager.error(err, SVNLogType.WC); } else if (SVNProperty.isWorkingCopyProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "''{0}'' is a wcprop, thus not accessible to clients", propName); SVNErrorManager.error(err, SVNLogType.WC); } else if (SVNProperty.isEntryProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Property ''{0}'' is an entry property", propName); SVNErrorManager.error(err, SVNLogType.WC); } SVNWCAccess wcAccess = createWCAccess(); try { wcAccess.probeOpen(path, true, admLockLevel); SVNEntry entry = wcAccess.getVersionedEntry(path, false); if (SVNDepth.FILES.compareTo(depth) <= 0 && entry.isDirectory()) { PropSetHandler entryHandler = new PropSetHandler(skipChecks, propName, propValue, handler, changeLists); wcAccess.walkEntries(path, entryHandler, false, depth); } else if (SVNWCAccess.matchesChangeList(changeLists, entry)) { boolean modified = SVNPropertiesManager.setProperty(wcAccess, path, propName, propValue, skipChecks); if (modified && handler != null) { handler.handleProperty(path, new SVNPropertyData(propName, propValue, getOptions())); } } } finally { wcAccess.close(); } } /** * Crawls the working copy at <code>path</code> and calls * {@link ISVNPropertyValueProvider#providePropertyValues(java.io.File,org.tmatesoft.svn.core.SVNProperties)} * to get properties to be change on each path being traversed * <p/> * If <code>depth</code> is {@link org.tmatesoft.svn.core.SVNDepth#EMPTY}, * change the properties on <code>path</code> only; if * {@link SVNDepth#FILES}, change the properties on <code>path</code> and * its file children (if any); if {@link SVNDepth#IMMEDIATES}, on * <code>path</code> and all of its immediate children (both files and * directories); if{@link SVNDepth#INFINITY}, on <code>path</code> and * everything beneath it. * <p/> * If <code>skipChecks</code> is <span class="javakeyword">true</span>, this * method does no validity checking of changed properties. But if * <code>skipChecks</code> is <span class="javakeyword">false</span>, and * changed property name is not a valid property for <code>path</code>, it * throws an exception, either with an error code * {@link org.tmatesoft.svn.core.SVNErrorCode#ILLEGAL_TARGET}(if the * property is not appropriate for <code>path</code>), or with * {@link org.tmatesoft.svn.core.SVNErrorCode#BAD_MIME_TYPE} (if changed * propery name is <span class="javastring">"svn:mime-type"</span>, but * changed property value is not a valid mime-type). * <p/> * <code>changeLists</code> is a collection of <code>String</code> * changelist names, used as a restrictive filter on items whose properties * are set; that is, don't set properties on any item unless it's a member * of one of those changelists. If <code>changelists</code> is empty (or * <span class="javakeyword">null</span>), no changelist filtering occurs. * <p> * This method operates only on working copies and does not open any network * connection. * * @param path * working copy path * @param propertyValueProvider * changed properties provider * @param skipChecks * <span class="javakeyword">true</span> to force the operation * to run without validity checking * @param depth * working copy tree depth to process * @param handler * a caller's property handler * @param changeLists * changelist names * @throws SVNException * <ul> * <li><code>path</code> does not exist <li>exception with * {@link SVNErrorCode#CLIENT_PROPERTY_NAME} error code - if * changed property name is a revision property name or not a * valid property name or not a regular property name (one * starting with a <span class="javastring">"svn:entry"</span> * or <span class="javastring">"svn:wc"</span> prefix) * </ul> * @see #doSetProperty(java.io.File,String,org.tmatesoft.svn.core.SVNPropertyValue,boolean,org.tmatesoft.svn.core.SVNDepth,ISVNPropertyHandler,java.util.Collection) * @since 1.2, SVN 1.5 */ public void doSetProperty(File path, ISVNPropertyValueProvider propertyValueProvider, boolean skipChecks, SVNDepth depth, ISVNPropertyHandler handler, Collection changeLists) throws SVNException { depth = depth == null ? SVNDepth.UNKNOWN : depth; int admLockLevel = SVNWCAccess.INFINITE_DEPTH; if (depth == SVNDepth.EMPTY || depth == SVNDepth.FILES) { admLockLevel = 0; } SVNWCAccess wcAccess = createWCAccess(); try { wcAccess.probeOpen(path, true, admLockLevel); SVNEntry entry = wcAccess.getVersionedEntry(path, false); if (SVNDepth.FILES.compareTo(depth) <= 0 && entry.isDirectory()) { PropSetHandlerExt entryHandler = new PropSetHandlerExt(skipChecks, propertyValueProvider, handler, changeLists); wcAccess.walkEntries(path, entryHandler, false, depth); } else if (SVNWCAccess.matchesChangeList(changeLists, entry)) { SVNAdminArea adminArea = entry.getAdminArea(); setLocalProperties(path, entry, adminArea, skipChecks, propertyValueProvider, handler); } } finally { wcAccess.close(); } } /** * Sets <code>propName</code> to <code>propValue</code> on <code>path</code> * . A <code>propValue</code> of <span class="javakeyword">null</span> will * delete the property. * <p/> * <code>baseRevision</code> must not be null; in this case, the property * will only be set if it has not changed since <code>baseRevision</code>. * <p/> * The {@link ISVNAuthenticationManager authentication manager} and * {@link ISVNCommitHandler commit handler}, either provided by a caller or * default ones, will be used to immediately attempt to commit the property * change in the repository. * <p/> * If <code>propName</code> is an svn-controlled property (i.e. prefixed * with <span class="javastring">"svn:"</span>), then the caller is * responsible for ensuring that the value uses LF line-endings. * <p/> * If <code>skipChecks</code> is <span class="javakeyword">true</span>, this * method does no validity checking. But if <code>skipChecks</code> is <span * class="javakeyword">false</span>, and <code>propName</code> is not a * valid property for <code>path</code>, it throws an exception, either with * an error code {@link org.tmatesoft.svn.core.SVNErrorCode#ILLEGAL_TARGET} * (if the property is not appropriate for <code>path</code>), or with * {@link org.tmatesoft.svn.core.SVNErrorCode#BAD_MIME_TYPE} (if * <code>propName</code> is <span class="javastring">"svn:mime-type"</span>, * but <code>propVal</code> is not a valid mime-type). * <p/> * If non-<span class="javakeyword">null</span>, * <code>revisionProperties</code> is an {@link SVNProperties} object * holding additional, custom revision properties (<code>String</code> names * mapped to <code>String</code> values) to be set on the new revision in * the event that this is a committing operation. This table cannot contain * any standard Subversion properties. * * @param url * versioned item url * @param propName * property name * @param propValue * property value * @param baseRevision * revision to change properties against * @param commitMessage * commit log message * @param revisionProperties * custom revision properties to set * @param skipChecks * <span class="javakeyword">true</span> to force the operation * to run without validity checking * @param handler * a caller's property handler * @return commit information if the commit succeeds * @throws SVNException * <ul> * <li><code>url</code> does not exist in <code>baseRevision * </code> <li>exception with * {@link SVNErrorCode#CLIENT_PROPERTY_NAME} error code - if * <code>propName</code> is a revision property name or not a * valid property name or not a regular property name (one * starting with an <span class="javastring">"svn:entry"</span> * or <span class="javastring">"svn:wc"</span> prefix) <li> * exception with {@link SVNErrorCode#UNSUPPORTED_FEATURE} error * code - if <code>propName</code> is either equal to * {@link SVNProperty#EOL_STYLE} or {@link SVNProperty#KEYWORDS} * or {@link SVNProperty#CHARSET} * </ul> * @see #doSetProperty(File,String,SVNPropertyValue,boolean,SVNDepth,ISVNPropertyHandler,Collection) * @since 1.2, SVN 1.5 */ public SVNCommitInfo doSetProperty(SVNURL url, String propName, SVNPropertyValue propValue, SVNRevision baseRevision, String commitMessage, SVNProperties revisionProperties, boolean skipChecks, ISVNPropertyHandler handler) throws SVNException { if (propValue != null && !SVNPropertiesManager.isValidPropertyName(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Bad property name ''{0}''", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNRevisionProperty.isRevisionProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Revision property ''{0}'' not allowed in this context", propName); SVNErrorManager.error(err, SVNLogType.WC); } else if (SVNProperty.isWorkingCopyProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "''{0}'' is a wcprop, thus not accessible to clients", propName); SVNErrorManager.error(err, SVNLogType.WC); } else if (SVNProperty.isEntryProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Property ''{0}'' is an entry property", propName); SVNErrorManager.error(err, SVNLogType.WC); } final SVNRepository repos = createRepository(url, null, null, true); long revNumber = SVNRepository.INVALID_REVISION; try { revNumber = getRevisionNumber(baseRevision, repos, null); } catch (SVNException svne) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Setting property on non-local target ''{0}'' needs a base revision", url); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNProperty.EOL_STYLE.equals(propName) || SVNProperty.KEYWORDS.equals(propName) || SVNProperty.CHARSET.equals(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Setting property ''{0}'' on non-local target ''{1}'' is not supported", new Object[] { propName, url }); SVNErrorManager.error(err, SVNLogType.WC); } SVNNodeKind kind = repos.checkPath("", revNumber); if (kind == SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Path ''{0}'' does not exist in revision {1}", new Object[] { url.getPath(), new Long(revNumber) }); SVNErrorManager.error(err, SVNLogType.WC); } if (propValue != null && SVNProperty.isSVNProperty(propName)) { final long baseRev = revNumber; propValue = SVNPropertiesManager.validatePropertyValue(url, kind, propName, propValue, skipChecks, getOptions(), new ISVNFileContentFetcher() { private SVNProperties myProperties = null; public void fetchFileContent(OutputStream os) throws SVNException { SVNProperties props = myProperties != null ? null : new SVNProperties(); repos.getFile("", baseRev, props, os); if (props != null) { myProperties = props; } } public SVNPropertyValue getProperty(String propertyName) throws SVNException { fetchFileProperties(); return myProperties.getSVNPropertyValue(propertyName); } private void fetchFileProperties() throws SVNException { if (myProperties == null) { myProperties = new SVNProperties(); repos.getFile("", baseRev, myProperties, null); } } public boolean fileIsBinary() throws SVNException { fetchFileProperties(); return Boolean.valueOf(SVNProperty.isBinaryMimeType(myProperties.getStringValue(SVNProperty.MIME_TYPE))); } }); } Collection commitItems = new ArrayList(2); SVNCommitItem commitItem = new SVNCommitItem(null, url, null, kind, SVNRevision.create(revNumber), SVNRevision.UNDEFINED, false, false, true, false, false, false); commitItems.add(commitItem); SVNCommitItem[] commitItemsArray = (SVNCommitItem[]) commitItems.toArray(new SVNCommitItem[commitItems.size()]); commitMessage = getCommitHandler() != null ? getCommitHandler().getCommitMessage(commitMessage, commitItemsArray) : (commitMessage == null ? "" : commitMessage); if (commitMessage == null) { return SVNCommitInfo.NULL; } commitMessage = SVNCommitUtil.validateCommitMessage(commitMessage); if (getCommitHandler() != null) { revisionProperties = getCommitHandler().getRevisionProperties(commitMessage, commitItemsArray, revisionProperties); } SVNPropertiesManager.validateRevisionProperties(revisionProperties); SVNCommitInfo commitInfo = null; ISVNEditor commitEditor = repos.getCommitEditor(commitMessage, null, true, revisionProperties, null); try { commitEditor.openRoot(revNumber); if (kind == SVNNodeKind.FILE) { commitEditor.openFile("", revNumber); commitEditor.changeFileProperty("", propName, propValue); commitEditor.closeFile("", null); } else { commitEditor.changeDirProperty(propName, propValue); } commitEditor.closeDir(); commitInfo = commitEditor.closeEdit(); } catch (SVNException svne) { commitEditor.abortEdit(); throw svne; } if (handler != null) { handler.handleProperty(url, new SVNPropertyData(propName, propValue, getOptions())); } return commitInfo; } /** * Set <code>propName</code> to <code>propValue</code> on revision * <code>revision</code> in the repository represented by <code>path</code>. * <p/> * This method simply obtains a url given a working path and calls * {@link #doSetRevisionProperty(SVNURL,SVNRevision,String,SVNPropertyValue,boolean,ISVNPropertyHandler)} * passing this url and the rest parameters. * * @param path * working copy path * @param revision * revision which properties are to be modified * @param propName * property name * @param propValue * property value * @param force * if <span class="javakeyword">true</span> allows newlines in * the author property * @param handler * caller's property handler * @throws SVNException * if one of the following is true: * <ul> * <li>exception with {@link SVNErrorCode#CLIENT_PROPERTY_NAME} * error code - if <code>propName</code> is invalid <li> * exceptions thrown by * {@link #doSetRevisionProperty(SVNURL,SVNRevision,String,SVNPropertyValue,boolean,ISVNPropertyHandler)} * </ul> */ public void doSetRevisionProperty(File path, SVNRevision revision, String propName, SVNPropertyValue propValue, boolean force, ISVNPropertyHandler handler) throws SVNException { if (propValue != null && !SVNPropertiesManager.isValidPropertyName(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Bad property name ''{0}''", propName); SVNErrorManager.error(err, SVNLogType.WC); } SVNURL url = getURL(path); doSetRevisionProperty(url, revision, propName, propValue, force, handler); } /** * Set <code>propName</code> to <code>propValue</code> on revision * <code>revision</code> in the repository represented by <code>path</code>. * A <code>propValue</code> of <span class="javakeyword">null</span> will * delete the property. The {@link ISVNAuthenticationManager authentication * manager}, either provided by a caller or a default one, will be used for * authentication. * <p/> * If <code>propName</code> is an svn-controlled property (i.e. prefixed * with <span class="javastring">"svn:"</span>), then the caller is * responsible for ensuring that the value is UTF8-encoded and uses LF * line-endings. * <p/> * Although this routine accepts a working copy path it doesn't affect the * working copy at all; it's a pure network operation that changes an * *unversioned* property attached to a revision. This can be used to tweak * log messages, dates, authors, and the like. Be careful: it's a lossy * operation. * <p> * Also note that unless the administrator creates a pre-revprop-change hook * in the repository, this feature will fail. * * @param url * repository URL * @param revision * revision which properties are to be modified * @param propName * property name * @param propValue * property value * @param force * if <span class="javakeyword">true</span> allows newlines in * the author property * @param handler * caller's property handler * @throws SVNException * if one of the following is true: * <ul> * <li>the operation can not be performed without forcing <li> * <code>propName</code> is either invalid or not a regular * property name (one starting with an <span * class="javastring">"svn:entry"</span> or <span * class="javastring">"svn:wc"</span> prefix) * </ul> * @see #doSetRevisionProperty(File,SVNRevision,String,SVNPropertyValue,boolean,ISVNPropertyHandler) */ public void doSetRevisionProperty(SVNURL url, SVNRevision revision, String propName, SVNPropertyValue propValue, boolean force, ISVNPropertyHandler handler) throws SVNException { if (propValue != null && !SVNPropertiesManager.isValidPropertyName(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Bad property name ''{0}''", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (!force && SVNRevisionProperty.AUTHOR.equals(propName) && propValue != null && propValue.isString() && propValue.getString().indexOf('\n') >= 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_REVISION_AUTHOR_CONTAINS_NEWLINE, "Value will not be set unless forced"); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNProperty.isWorkingCopyProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "''{0}'' is a wcprop, thus not accessible to clients", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNProperty.isEntryProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Property ''{0}'' is an entry property", propName); SVNErrorManager.error(err, SVNLogType.WC); } SVNRepository repos = createRepository(url, null, null, SVNRevision.UNDEFINED, revision, null); long revNumber = getRevisionNumber(revision, repos, null); repos.setRevisionPropertyValue(revNumber, propName, propValue); if (handler != null) { handler.handleProperty(revNumber, new SVNPropertyData(propName, propValue, getOptions())); } } /** * Gets the value of the property <code>propName</code> for * <code>path</code>. This method simply creates an implementation of * {@link ISVNPropertyHandler} which stores the value only for * <code>path</code> which is then used in the following call to * * <code>doGetProperty(path, propName, pegRevision, revision, SVNDepth.EMPTY, handler, null)</code> * . * * @param path * a WC item's path * @param propName * an item's property name; if it's <span * class="javakeyword">null</span> then all the item's properties * will be retrieved but only the first of them returned * @param pegRevision * a revision in which the item is first looked up * @param revision * a target revision; * @return the item's property * @throws SVNException * if one of the following is true: * <ul> * <li><code>propName</code> starts with the * {@link org.tmatesoft.svn.core.SVNProperty#SVN_WC_PREFIXsvn * :wc:} prefix * <li><code>path</code> is not under version control * </ul> * @see #doGetProperty(File,String,SVNRevision,SVNRevision,SVNDepth,ISVNPropertyHandler,Collection) */ public SVNPropertyData doGetProperty(final File path, String propName, SVNRevision pegRevision, SVNRevision revision) throws SVNException { final SVNPropertyData[] data = new SVNPropertyData[1]; doGetProperty(path, propName, pegRevision, revision, SVNDepth.EMPTY, new ISVNPropertyHandler() { public void handleProperty(File file, SVNPropertyData property) { if (data[0] == null && path.equals(file)) { data[0] = property; } } public void handleProperty(SVNURL url, SVNPropertyData property) { } public void handleProperty(long revision, SVNPropertyData property) { } }, null); return data[0]; } /** * Gets the value of the property <code>propName</code> for <code>url</code> * . This method simply creates an implementation of * {@link ISVNPropertyHandler} which stores the value only for * <code>path</code> which is then used in the following call to * <code>doGetProperty(url, propName, pegRevision, revision, SVNDepth.EMPTY, handler)</code> * . * * @param url * an item's repository location * @param propName * an item's property name; if it's <span * class="javakeyword">null</span> then all the item's properties * will be retrieved but only the first of them returned * @param pegRevision * a revision in which the item is first looked up * @param revision * a target revision; * @return the item's property * @throws SVNException * if one of the following is true: * <ul> * <li><code>propName</code> starts with the * {@link org.tmatesoft.svn.core.SVNProperty#SVN_WC_PREFIXsvn * :wc:} prefix * <li><code>path</code> is not under version control * </ul> * @see #doGetProperty(SVNURL,String,SVNRevision,SVNRevision,SVNDepth,ISVNPropertyHandler) */ public SVNPropertyData doGetProperty(final SVNURL url, String propName, SVNRevision pegRevision, SVNRevision revision) throws SVNException { final SVNPropertyData[] data = new SVNPropertyData[1]; doGetProperty(url, propName, pegRevision, revision, SVNDepth.EMPTY, new ISVNPropertyHandler() { public void handleProperty(File file, SVNPropertyData property) { } public void handleProperty(long revision, SVNPropertyData property) { } public void handleProperty(SVNURL location, SVNPropertyData property) throws SVNException { if (data[0] == null && url.toString().equals(location.toString())) { data[0] = property; } } }); return data[0]; } /** * Gets an item's versioned property and passes it to a provided property * handler. It's possible to get either a local property (from a Working * Copy) or a remote one (located in a repository). If <vode>revision</code> * is one of: * <ul> * <li>{@link SVNRevision#BASE BASE} * <li>{@link SVNRevision#WORKING WORKING} * <li>{@link SVNRevision#COMMITTED COMMITTED} * </ul> * then the result is a WC item's property. Otherwise the property is taken * from a repository (using the item's URL). * * @param path * a WC item's path * @param propName * an item's property name; if it's <span * class="javakeyword">null</span> then all the item's properties * will be retrieved and passed to <code>handler</code> for * processing * @param pegRevision * a revision in which the item is first looked up * @param revision * a target revision; * @param recursive * <span class="javakeyword">true</span> to descend recursively * @param handler * a caller's property handler * @throws SVNException * if one of the following is true: * <ul> * <li><code>propName</code> starts with the * {@link org.tmatesoft.svn.core.SVNProperty#SVN_WC_PREFIXsvn * :wc:} prefix * <li><code>path</code> is not under version control * </ul> * @deprecated use * {@link #doGetProperty(File,String,SVNRevision,SVNRevision,SVNDepth,ISVNPropertyHandler,Collection)} * instead */ public void doGetProperty(File path, String propName, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNPropertyHandler handler) throws SVNException { doGetProperty(path, propName, pegRevision, revision, SVNDepth.getInfinityOrEmptyDepth(recursive), handler, null); } /** * Invokes <code>handler</code> on paths covered by <code>depth</code> * starting with the specified <code>path</code>. * <p/> * If both <vode>revision</code> and <code>pegRevision</code> are ones of: * <ul> * <li>{@link SVNRevision#BASE BASE} * <li>{@link SVNRevision#WORKING WORKING} * <li>{@link SVNRevision#COMMITTED COMMITTED} * <li>{@link SVNRevision#UNDEFINED} * </ul> * then this method gets properties from the working copy without connecting * to the repository. Otherwise properties are taken from the repository * (using the item's URL). * <p/> * The actual node revision selected is determined by the path as it exists * in <code>pegRevision</code>. If <code>pegRevision</code> is * {@link SVNRevision#UNDEFINED}, then it defaults to * {@link SVNRevision#WORKING}. * <p/> * If <code>depth</code> is {@link SVNDepth#EMPTY}, fetch the property from * <code>path</code> only; if {@link SVNDepth#FILES}, fetch from * <code>path</code> and its file children (if any); if * {@link SVNDepth#IMMEDIATES}, from <code>path</code> and all of its * immediate children (both files and directories); if * {@link SVNDepth#INFINITY}, from <code>path</code> and everything beneath * it. * <p/> * <code>changeLists</code> is a collection of <code>String</tt> changelist * names, used as a restrictive filter on items whose properties are * set; that is, don't set properties on any item unless it's a member * of one of those changelists. If <code>changeLists</code> is empty (or <span class="javakeyword">null</span>), no * changelist filtering occurs. * * @param path * a WC item's path * @param propName * an item's property name; if it's <span * class="javakeyword">null</span> then all the item's properties * will be retrieved and passed to <code>handler</code> for * processing * @param pegRevision * a revision in which the item is first looked up * @param revision * a target revision * @param depth * tree depth * @param handler * a caller's property handler * @param changeLists * collection of changelist names * @throws SVNException * if one of the following is true: * <ul> * <li><code>propName</code> starts with the * {@link org.tmatesoft.svn.core.SVNProperty#SVN_WC_PREFIX} * prefix <li><code>path</code> is not under version control * </ul> * @since 1.2, SVN 1.5 */ public void doGetProperty(File path, String propName, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, ISVNPropertyHandler handler, Collection changeLists) throws SVNException { if (SVNProperty.isWorkingCopyProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "''{0}'' is a wcprop, thus not accessible to clients", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNProperty.isEntryProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Property ''{0}'' is an entry property", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (depth == null || depth == SVNDepth.UNKNOWN) { depth = SVNDepth.EMPTY; } if ((revision != SVNRevision.WORKING && revision != SVNRevision.BASE && revision != SVNRevision.COMMITTED && revision != SVNRevision.UNDEFINED) || (pegRevision != SVNRevision.WORKING && pegRevision != SVNRevision.BASE && pegRevision != SVNRevision.COMMITTED && pegRevision != SVNRevision.UNDEFINED)) { long[] revNum = { SVNRepository.INVALID_REVISION }; SVNRepository repository = createRepository(null, path, null, pegRevision, revision, revNum); revision = SVNRevision.create(revNum[0]); doGetRemoteProperty(repository.getLocation(), "", repository, propName, revision, depth, handler); } else { SVNWCAccess wcAccess = createWCAccess(); try { int admDepth = getLevelsToLockFromDepth(depth); SVNAdminArea area = wcAccess.probeOpen(path, false, admDepth); SVNEntry entry = wcAccess.getVersionedEntry(path, false); boolean base = revision == SVNRevision.BASE || revision == SVNRevision.COMMITTED; doGetLocalProperty(entry, area, propName, base, handler, depth, changeLists); } finally { wcAccess.close(); } } } /** * Gets an item's versioned property from a repository and passes it to a * provided property handler. This method is useful when having no Working * Copy at all. * * @param url * an item's repository location * @param propName * an item's property name; if it's <span * class="javakeyword">null</span> then all the item's properties * will be retrieved and passed to <code>handler</code> for * processing * @param pegRevision * a revision in which the item is first looked up * @param revision * a target revision * @param recursive * <span class="javakeyword">true</span> to descend recursively * @param handler * a caller's property handler * @throws SVNException * if <code>propName</code> starts with the * {@link org.tmatesoft.svn.core.SVNProperty#SVN_WC_PREFIXsvn * :wc:} prefix * @deprecated use * {@link #doGetProperty(SVNURL,String,SVNRevision,SVNRevision,SVNDepth,ISVNPropertyHandler)} * instead */ public void doGetProperty(SVNURL url, String propName, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNPropertyHandler handler) throws SVNException { doGetProperty(url, propName, pegRevision, revision, SVNDepth.getInfinityOrEmptyDepth(recursive), handler); } /** * Invokes <code>handler</code> on paths covered by <code>depth</code> * starting with the specified <code>path</code>. * <p/> * If <code></code> is {@link SVNRevision#UNDEFINED} then get properties * from the repository head. Else get the properties as of * <code>revision</code>. The actual node revision selected is determined by * the path as it exists in <code>pegRevision</code>. If * <code>pegRevision</code> is {@link SVNRevision#UNDEFINED}, then it * defaults to {@link SVNRevision#HEAD}. * <p/> * If <code>depth</code> is {@link SVNDepth#EMPTY}, fetch the property from * <code>path</code> only; if {@link SVNDepth#FILES}, fetch from * <code>path</code> and its file children (if any); if * {@link SVNDepth#IMMEDIATES}, from <code>path</code> and all of its * immediate children (both files and directories); if * {@link SVNDepth#INFINITY}, from <code>path</code> and everything beneath * it. * * @param url * versioned item url * @param propName * an item's property name; if it's <span * class="javakeyword">null</span> then all the item's properties * will be retrieved and passed to <code>handler</code> for * processing * @param pegRevision * a revision in which the item is first looked up * @param revision * a target revision * @param depth * tree depth * @param handler * a caller's property handler * @throws SVNException * if one of the following is true: * <ul> * <li><code>propName</code> starts with the * {@link org.tmatesoft.svn.core.SVNProperty#SVN_WC_PREFIX} * prefix <li><code>path</code> is not under version control * </ul> * @since 1.2, SVN 1.5 */ public void doGetProperty(SVNURL url, String propName, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, ISVNPropertyHandler handler) throws SVNException { if (SVNProperty.isWorkingCopyProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "''{0}'' is a wcprop, thus not accessible to clients", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNProperty.isEntryProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Property ''{0}'' is an entry property", propName); SVNErrorManager.error(err, SVNLogType.WC); } long[] pegRev = new long[] { -1 }; SVNRepository repos = createRepository(url, null, null, pegRevision, revision, pegRev); revision = pegRev[0] < 0 ? revision : SVNRevision.create(pegRev[0]); doGetRemoteProperty(url, "", repos, propName, revision, depth, handler); } /** * Gets an unversioned revision property from a repository (getting a * repository URL from a Working Copy) and passes it to a provided property * handler. * * @param path * a local Working Copy item which repository location is used to * connect to a repository * @param propName * a revision property name; if this parameter is <span * class="javakeyword">null</span> then all the revision * properties will be retrieved and passed to * <code>handler</code> for processing * @param revision * a revision which property is to be retrieved * @param handler * a caller's property handler * @throws SVNException * if one of the following is true: * <ul> * <li><code>revision</code> is invalid * <li><code>propName</code> starts with the * {@link org.tmatesoft.svn.core.SVNProperty#SVN_WC_PREFIXsvn * :wc:} prefix * </ul> * @see #doGetRevisionProperty(SVNURL,String,SVNRevision,ISVNPropertyHandler) */ public void doGetRevisionProperty(File path, String propName, SVNRevision revision, ISVNPropertyHandler handler) throws SVNException { if (SVNProperty.isWorkingCopyProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "''{0}'' is a wcprop, thus not accessible to clients", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNProperty.isEntryProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Property ''{0}'' is an entry property", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (!revision.isValid()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Valid revision have to be specified to fetch revision property"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } SVNRepository repository = createRepository(null, path, null, SVNRevision.UNDEFINED, revision, null); long revisionNumber = getRevisionNumber(revision, repository, path); doGetRevisionProperty(repository, propName, revisionNumber, handler); } /** * Gets an unversioned revision property from a repository and passes it to * a provided property handler. * * @param url * a URL pointing to a repository location which revision * property is to be got * @param propName * a revision property name; if this parameter is <span * class="javakeyword">null</span> then all the revision * properties will be retrieved and passed to * <code>handler</code> for processing * @param revision * a revision which property is to be retrieved * @param handler * a caller's property handler * @return actual revision number to which <code>revision</code> is resolved * @throws SVNException * if one of the following is true: * <ul> * <li><code>revision</code> is invalid * <li><code>propName</code> starts with the * {@link org.tmatesoft.svn.core.SVNProperty#SVN_WC_PREFIXsvn * :wc:} prefix * </ul> * @see #doGetRevisionProperty(File,String,SVNRevision,ISVNPropertyHandler) */ public long doGetRevisionProperty(SVNURL url, String propName, SVNRevision revision, ISVNPropertyHandler handler) throws SVNException { if (SVNProperty.isWorkingCopyProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "''{0}'' is a wcprop, thus not accessible to clients", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNProperty.isEntryProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Property ''{0}'' is an entry property", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (!revision.isValid()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Valid revision have to be specified to fetch revision property"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } SVNRepository repos = createRepository(url, null, null, true); long revNumber = getRevisionNumber(revision, repos, null); doGetRevisionProperty(repos, propName, revNumber, handler); return revNumber; } /** * Schedules a Working Copy item for deletion. This method is equivalent to * <code>doDelete(path, force, true, dryRun)</code>. * * @param path * a WC item to be deleted * @param force * <span class="javakeyword">true</span> to force the operation * to run * @param dryRun * <span class="javakeyword">true</span> only to try the delete * operation without actual deleting * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> is not under version control * <li>can not delete <code>path</code> without forcing * </ul> * @see #doDelete(File,boolean,boolean,boolean) */ public void doDelete(File path, boolean force, boolean dryRun) throws SVNException { doDelete(path, force, true, dryRun); } /** * Schedules a Working Copy item for deletion. This method allows to choose * - whether file item(s) are to be deleted from the filesystem or not. * Another version of the {@link #doDelete(File,boolean,boolean) doDelete()} * method is similar to the corresponding SVN client's command - <code>'svn delete'</code> as * it always deletes files from the filesystem. * <p/> * This method deletes only local working copy paths without connecting to * the repository. * * @param path * a WC item to be deleted * @param force * <span class="javakeyword">true</span> to force the operation * to run * @param deleteFiles * if <span class="javakeyword">true</span> then files will be * scheduled for deletion as well as deleted from the filesystem, * otherwise files will be only scheduled for addition and still * be present in the filesystem * @param dryRun * <span class="javakeyword">true</span> only to try the delete * operation without actual deleting * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> is not under version control <li>can * not delete <code>path</code> without forcing * </ul> */ public void doDelete(File path, boolean force, boolean deleteFiles, boolean dryRun) throws SVNException { SVNWCAccess wcAccess = createWCAccess(); path = path.getAbsoluteFile(); try { if (!force && deleteFiles) { SVNWCManager.canDelete(path, getOptions(), this); } SVNAdminArea root = wcAccess.open(path.getParentFile(), true, 0); if (!dryRun) { SVNWCManager.delete(wcAccess, root, path, deleteFiles, true); } } finally { wcAccess.close(); } } /** * Schedules an unversioned item for addition to a repository thus putting * it under version control. * <p/> * <p/> * To create and add to version control a new directory, set * <code>mkdir</code> to <span class="javakeyword">true</span>. * <p/> * <p/> * Calling this method is equivalent to * <code>doAdd(path, force, mkdir, climbUnversionedParents, recursive, false)</code>. * * @param path * a path to be put under version control (will be added to a * repository in next commit) * @param force * when <span class="javakeyword">true</span> forces the * operation to run on already versioned files or directories * without reporting error. When ran recursively, all unversioned * files and directories in a tree will be scheduled for * addition. * @param mkdir * if <span class="javakeyword">true</span> - creates a new * directory and schedules it for addition * @param climbUnversionedParents * if <span class="javakeyword">true</span> and <code>path</code> * is located in an unversioned parent directory then the parent * will be automatically scheduled for addition, too * @param recursive * <span class="javakeyword">true</span> to descend recursively * (relevant for directories) * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> doesn't belong to a Working Copy <li> * <code>path</code> doesn't exist and <code>mkdir</code> is * <span class="javakeyword">false</span> <li><code>path</code> * is the root directory of the Working Copy * @deprecated use * {@link #doAdd(File,boolean,boolean,boolean,SVNDepth,boolean,boolean)} * instead */ public void doAdd(File path, boolean force, boolean mkdir, boolean climbUnversionedParents, boolean recursive) throws SVNException { SVNDepth depth = SVNDepth.getInfinityOrEmptyDepth(recursive); doAdd(path, force, mkdir, climbUnversionedParents, depth, false, false, climbUnversionedParents); } /** * Schedules an unversioned item for addition to a repository thus putting * it under version control. * <p/> * <p/> * To create and add to version control a new directory, set * <code>mkdir</code> to <span class="javakeyword">true</span>. * * @param path * a path to be put under version control (will be added to a * repository in next commit) * @param force * when <span class="javakeyword">true</span> forces the * operation to run on already versioned files or directories * without reporting error. When ran recursively, all unversioned * files and directories in a tree will be scheduled for * addition. * @param mkdir * if <span class="javakeyword">true</span> - creates a new * directory and schedules it for addition * @param climbUnversionedParents * if <span class="javakeyword">true</span> and <code>path</code> * is located in an unversioned parent directory then the parent * will be automatically scheduled for addition, too * @param recursive * <span class="javakeyword">true</span> to descend recursively * (relevant for directories) * @param includeIgnored * controls whether ignored items must be also added * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> doesn't belong to a Working Copy <li> * <code>path</code> doesn't exist and <code>mkdir</code> is * <span class="javakeyword">false</span> <li><code>path</code> * is the root directory of the Working Copy * </ul> * @since 1.1 * @deprecated use * {@link #doAdd(File,boolean,boolean,boolean,SVNDepth,boolean,boolean)} * instead */ public void doAdd(File path, boolean force, boolean mkdir, boolean climbUnversionedParents, boolean recursive, boolean includeIgnored) throws SVNException { SVNDepth depth = SVNDepth.getInfinityOrEmptyDepth(recursive); doAdd(path, force, mkdir, climbUnversionedParents, depth, false, includeIgnored, climbUnversionedParents); } /** * Schedules a working copy <code>path</code> for addition to the * repository. * <p/> * If <code>depth</code> is {@link SVNDepth#EMPTY}, adds just * <code>path</code> and nothing below it. If {@link SVNDepth#FILES}, adds * <code>path</code> and any file children of <code>path</code>. If * {@link SVNDepth#IMMEDIATES}, adds <code>path</code>, any file children, * and any immediate subdirectories (but nothing underneath those * subdirectories). If {@link SVNDepth#INFINITY}, adds <code>path</code> and * everything under it fully recursively. * <p/> * <code>path</code>'s parent must be under revision control already (unless * <code>makeParents</code> is <span class="javakeyword">true</span>), but * <code>path</code> is not. * <p/> * If <code>force</code> is set, <code>path</code> is a directory, * <code>depth</code> is {@link SVNDepth#INFINITY}, then schedules for * addition unversioned files and directories scattered deep within a * versioned tree. * <p/> * If <code>includeIgnored</code> is <span class="javakeyword">false</span>, * doesn't add files or directories that match ignore patterns. * <p/> * If <code>makeParents</code> is <span class="javakeyword">true</span>, * recurse up <code>path</code>'s directory and look for a versioned * directory. If found, add all intermediate paths between it and * <code>path</code>. * <p/> * Important: this is a *scheduling* operation. No changes will happen to * the repository until a commit occurs. This scheduling can be removed with * a call to {@link #doRevert(File[],SVNDepth,Collection)}. * * @param path * working copy path * @param force * if <span class="javakeyword">true</span>, this method does not * throw exceptions on already-versioned items * @param mkdir * if <span class="javakeyword">true</span>, create a directory * also at <code>path</code> * @param climbUnversionedParents * not used; make use of <code>makeParents</code> instead * @param depth * tree depth * @param includeIgnored * if <span class="javakeyword">true</span>, does not apply * ignore patterns to paths being added * @param makeParents * if <span class="javakeyword">true</span>, climb upper and * schedule also all unversioned paths in the way * @throws SVNException * <ul> * <li/>exception with {@link SVNErrorCode#ENTRY_EXISTS} error * code - if <code>force</code> is not set and <code>path</code> * is already under version <li/>exception with * {@link SVNErrorCode#CLIENT_NO_VERSIONED_PARENT} error code - * if <code>makeParents</code> is <span * class="javakeyword">true</span> but no unversioned paths * stepping upper from <code>path</code> are found * @since 1.2, SVN 1.5 */ public void doAdd(File path, boolean force, boolean mkdir, boolean climbUnversionedParents, SVNDepth depth, boolean includeIgnored, boolean makeParents) throws SVNException { doAdd(path, force, mkdir, climbUnversionedParents, depth, false, includeIgnored, makeParents); } /** * Schedules working copy <code>paths</code> for addition to the repository. * <p/> * If <code>depth</code> is {@link SVNDepth#EMPTY}, adds just * <code>paths</code> and nothing below it. If {@link SVNDepth#FILES}, adds * <code>paths</code> and any file children of <code>paths</code>. If * {@link SVNDepth#IMMEDIATES}, adds <code>paths</code>, any file children, * and any immediate subdirectories (but nothing underneath those * subdirectories). If {@link SVNDepth#INFINITY}, adds <code>paths</code> * and everything under it fully recursively. * <p/> * <code>paths</code>' parent must be under revision control already (unless * <code>makeParents</code> is <span class="javakeyword">true</span>), but * <code>paths</code> are not. * <p/> * If <code>force</code> is set, path is a directory, <code>depth</code> is * {@link SVNDepth#INFINITY}, then schedules for addition unversioned files * and directories scattered deep within a versioned tree. * <p/> * If <code>includeIgnored</code> is <span class="javakeyword">false</span>, * doesn't add files or directories that match ignore patterns. * <p/> * If <code>makeParents</code> is <span class="javakeyword">true</span>, * recurse up path's directory and look for a versioned directory. If found, * add all intermediate paths between it and the path. * <p/> * Important: this is a *scheduling* operation. No changes will happen to * the repository until a commit occurs. This scheduling can be removed with * a call to {@link #doRevert(File[],SVNDepth,Collection)}. * * @param paths * working copy paths to add * @param force * if <span class="javakeyword">true</span>, this method does not * throw exceptions on already-versioned items * @param mkdir * if <span class="javakeyword">true</span>, create a directory * also at <code>path</code> * @param climbUnversionedParents * not used; make use of <code>makeParents</code> instead * @param depth * tree depth * @param depthIsSticky * if depth should be recorded to the working copy * @param includeIgnored * if <span class="javakeyword">true</span>, does not apply * ignore patterns to paths being added * @param makeParents * if <span class="javakeyword">true</span>, climb upper and * schedule also all unversioned paths in the way * @throws SVNException * <ul> * <li/>exception with {@link SVNErrorCode#ENTRY_EXISTS} error * code - if <code>force</code> is not set and a path is already * under version <li/>exception with * {@link SVNErrorCode#CLIENT_NO_VERSIONED_PARENT} error code - * if <code>makeParents</code> is <span * class="javakeyword">true</span> but no unversioned paths * stepping upper from a path are found * @since 1.3 */ public void doAdd(File[] paths, boolean force, boolean mkdir, boolean climbUnversionedParents, SVNDepth depth, boolean depthIsSticky, boolean includeIgnored, boolean makeParents) throws SVNException { setEventPathPrefix(""); try { if (paths != null) { for (int i = 0; i < paths.length; i++) { doAdd(paths[i], force, mkdir, climbUnversionedParents, depth, depthIsSticky, includeIgnored, makeParents); } } } finally { setEventPathPrefix(null); } } /** * Schedules a working copy <code>path</code> for addition to the * repository. * <p/> * If <code>depth</code> is {@link SVNDepth#EMPTY}, adds just * <code>path</code> and nothing below it. If {@link SVNDepth#FILES}, adds * <code>path</code> and any file children of <code>path</code>. If * {@link SVNDepth#IMMEDIATES}, adds <code>path</code>, any file children, * and any immediate subdirectories (but nothing underneath those * subdirectories). If {@link SVNDepth#INFINITY}, adds <code>path</code> and * everything under it fully recursively. * <p/> * <code>path</code>'s parent must be under revision control already (unless * <code>makeParents</code> is <span class="javakeyword">true</span>), but * <code>path</code> is not. * <p/> * If <code>force</code> is set, <code>path</code> is a directory, * <code>depth</code> is {@link SVNDepth#INFINITY}, then schedules for * addition unversioned files and directories scattered deep within a * versioned tree. * <p/> * If <code>includeIgnored</code> is <span class="javakeyword">false</span>, * doesn't add files or directories that match ignore patterns. * <p/> * If <code>makeParents</code> is <span class="javakeyword">true</span>, * recurse up <code>path</code>'s directory and look for a versioned * directory. If found, add all intermediate paths between it and * <code>path</code>. * <p/> * Important: this is a *scheduling* operation. No changes will happen to * the repository until a commit occurs. This scheduling can be removed with * a call to {@link #doRevert(File[],SVNDepth,Collection)}. * * @param path * working copy path * @param force * if <span class="javakeyword">true</span>, this method does not * throw exceptions on already-versioned items * @param mkdir * if <span class="javakeyword">true</span>, create a directory * also at <code>path</code> * @param climbUnversionedParents * not used; make use of <code>makeParents</code> instead * @param depth * tree depth * @param depthIsSticky * if depth should be recorded to the working copy * @param includeIgnored * if <span class="javakeyword">true</span>, does not apply * ignore patterns to paths being added * @param makeParents * if <span class="javakeyword">true</span>, climb upper and * schedule also all unversioned paths in the way * @throws SVNException * <ul> * <li/>exception with {@link SVNErrorCode#ENTRY_EXISTS} error * code - if <code>force</code> is not set and <code>path</code> * is already under version <li/>exception with * {@link SVNErrorCode#CLIENT_NO_VERSIONED_PARENT} error code - * if <code>makeParents</code> is <span * class="javakeyword">true</span> but no unversioned paths * stepping upper from <code>path</code> are found * @since 1.3 */ public void doAdd(File path, boolean force, boolean mkdir, boolean climbUnversionedParents, SVNDepth depth, boolean depthIsSticky, boolean includeIgnored, boolean makeParents) throws SVNException { depth = depth == null ? SVNDepth.UNKNOWN : depth; path = path.getAbsoluteFile(); if (!mkdir && makeParents && path.getParentFile() != null) { SVNWCAccess wcAccess = createWCAccess(); try { addParentDirectories(wcAccess, path.getParentFile()); } finally { wcAccess.close(); } } SVNFileType kind = SVNFileType.getType(path); if (force && mkdir && kind == SVNFileType.DIRECTORY) { doAdd(path, force, false, true, SVNDepth.EMPTY, depthIsSticky, true, makeParents); return; } else if (mkdir) { File parent = path; File firstCreated = path; while (parent != null && SVNFileType.getType(parent) == SVNFileType.NONE) { if (!parent.equals(path) && !makeParents) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot create directoy ''{0}'' with non-existent parents", path); SVNErrorManager.error(err, SVNLogType.WC); } firstCreated = parent; parent = parent.getParentFile(); } boolean created = path.mkdirs(); if (!created) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot create new directory ''{0}''", path); while (parent == null ? path != null : !path.equals(parent)) { SVNFileUtil.deleteAll(path, true); path = path.getParentFile(); } SVNErrorManager.error(err, SVNLogType.WC); } try { doAdd(firstCreated, false, false, climbUnversionedParents, depth, depthIsSticky, true, makeParents); } catch (SVNException e) { SVNFileUtil.deleteAll(firstCreated, true); throw e; } return; } SVNWCAccess wcAccess = createWCAccess(); try { SVNAdminArea dir = null; SVNFileType fileType = SVNFileType.getType(path); if (fileType == SVNFileType.DIRECTORY) { dir = wcAccess.open(SVNWCUtil.isVersionedDirectory(path.getParentFile()) ? path.getParentFile() : path, true, 0); } else { dir = wcAccess.open(path.getParentFile(), true, 0); } if (fileType == SVNFileType.DIRECTORY && depth.compareTo(SVNDepth.FILES) >= 0) { File wcRoot = SVNWCUtil.getWorkingCopyRoot(dir.getRoot(), true); addDirectory(wcRoot, path, dir, force, includeIgnored, depth, depthIsSticky); } else if (fileType == SVNFileType.FILE || fileType == SVNFileType.SYMLINK) { addFile(path, fileType, dir); } else { SVNWCManager.add(path, dir, null, SVNRevision.UNDEFINED, depthIsSticky ? depth : null); } } catch (SVNException e) { if (!(force && e.getErrorMessage().getErrorCode() == SVNErrorCode.ENTRY_EXISTS)) { throw e; } } finally { wcAccess.close(); } } /** * Schedules <code>path</code> as being replaced. This method does not * perform any deletion\addition in the filesysem nor does it require a * connection to the repository. It just marks the current <code>path</code> * item as being replaced. * * @param path * working copy path to mark as * @throws SVNException * @since 1.2 */ public void doMarkReplaced(File path) throws SVNException { SVNWCAccess wcAccess = createWCAccess(); path = path.getAbsoluteFile(); try { SVNAdminAreaInfo areaInfo = wcAccess.openAnchor(path, true, SVNWCAccess.INFINITE_DEPTH); SVNAdminArea anchor = areaInfo.getAnchor(); if (path.equals(anchor.getRoot().getAbsoluteFile())) { SVNWCManager.markTree(anchor, SVNProperty.SCHEDULE_REPLACE, false, false, SVNWCManager.SCHEDULE); } else { SVNEntry entry = anchor.getEntry(path.getName(), false); SVNWCManager.markEntry(anchor, entry, SVNProperty.SCHEDULE_REPLACE, false, false, SVNWCManager.SCHEDULE); } anchor.saveEntries(false); } finally { wcAccess.close(); } } /** * Reverts all local changes made to a Working Copy item(s) thus bringing it * to a 'pristine' state. * * @param path * a WC path to perform a revert on * @param recursive * <span class="javakeyword">true</span> to descend recursively * (relevant for directories) * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> is not under version control * <li>when trying to revert an addition of a directory from * within the directory itself * </ul> * @see #doRevert(File[],boolean) * @deprecated use {@link #doRevert(File[],SVNDepth,Collection)} */ public void doRevert(File path, boolean recursive) throws SVNException { doRevert(new File[] { path }, SVNDepth.getInfinityOrEmptyDepth(recursive), null); } /** * Reverts all local changes made to a Working Copy item(s) thus bringing it * to a 'pristine' state. * * @param paths * a WC paths to perform a revert on * @param recursive * <span class="javakeyword">true</span> to descend recursively * (relevant for directories) * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> is not under version control * <li>when trying to revert an addition of a directory from * within the directory itself * </ul> * <p/> * Exception will not be thrown if there are multiple paths * passed. Instead caller should process events received by * <code>ISVNEventHandler</code> instance to get information on * whether certain path was reverted or not. * @deprecated use {@link #doRevert(File[],SVNDepth,Collection)} instead */ public void doRevert(File[] paths, boolean recursive) throws SVNException { doRevert(paths, recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY, null); } /** * Restores the pristine version of working copy <code>paths</code>, * effectively undoing any local mods. For each path in <code>paths</code>, * reverts it if it is a file. Else if it is a directory, reverts according * to <code>depth</code>: * <p/> * If </code>depth</code> is {@link SVNDepth#EMPTY}, reverts just the * properties on the directory; else if {@link SVNDepth#FILES}, reverts the * properties and any files immediately under the directory; else if * {@link SVNDepth#IMMEDIATES}, reverts all of the preceding plus properties * on immediate subdirectories; else if {@link SVNDepth#INFINITY}, reverts * path and everything under it fully recursively. * <p/> * <code>changeLists</code> is a collection of <code>String</code> * changelist names, used as a restrictive filter on items reverted; that * is, doesn't revert any item unless it's a member of one of those * changelists. If <code>changeLists</code> is empty (or <span * class="javakeyword">null</span>), no changelist filtering occurs. * <p/> * If an item specified for reversion is not under version control, then * does not fail with an exception, just invokes {@link ISVNEventHandler} * using notification code {@link SVNEventAction#SKIP}. * * @param paths * working copy paths to revert * @param depth * tree depth * @param changeLists * collection with changelist names * @throws SVNException * @since 1.2, SVN 1.5 */ public void doRevert(File[] paths, SVNDepth depth, Collection changeLists) throws SVNException { boolean reverted = false; try { for (int i = 0; i < paths.length; i++) { File path = paths[i]; path = path.getAbsoluteFile(); SVNWCAccess wcAccess = createWCAccess(); try { int admLockLevel = getLevelsToLockFromDepth(depth); SVNAdminAreaInfo info = wcAccess.openAnchor(path, true, admLockLevel); SVNEntry entry = wcAccess.getEntry(path, false); if (entry != null && entry.isDirectory() && entry.isScheduledForAddition()) { if (depth != SVNDepth.INFINITY) { depth = SVNDepth.INFINITY; wcAccess.close(); info = wcAccess.openAnchor(path, true, SVNWCAccess.INFINITE_DEPTH); } } boolean useCommitTimes = getOptions().isUseCommitTimes(); reverted |= doRevert(path, info.getAnchor(), depth, useCommitTimes, changeLists); } catch (SVNException e) { reverted |= true; SVNErrorCode code = e.getErrorMessage().getErrorCode(); if (code == SVNErrorCode.ENTRY_NOT_FOUND || code == SVNErrorCode.UNVERSIONED_RESOURCE) { SVNEvent event = SVNEventFactory.createSVNEvent(path, SVNNodeKind.UNKNOWN, null, SVNRepository.INVALID_REVISION, SVNEventAction.SKIP, SVNEventAction.REVERT, null, null); dispatchEvent(event); continue; } throw e; } finally { wcAccess.close(); } } } finally { if (reverted) { sleepForTimeStamp(); } } } /** * Resolves a 'conflicted' state on a Working Copy item. * * @param path * a WC item to be resolved * @param recursive * <span class="javakeyword">true</span> to descend recursively * (relevant for directories) - this will resolve the entire tree * @throws SVNException * if <code>path</code> is not under version control * @deprecated use {@link #doResolve(File,SVNDepth,SVNConflictChoice)} * instead */ public void doResolve(File path, boolean recursive) throws SVNException { doResolve(path, SVNDepth.fromRecurse(recursive), SVNConflictChoice.MERGED); } /** * Performs automatic conflict resolution on a working copy * <code>path</code>. * <p/> * If <code>depth</code> is {@link SVNDepth#EMPTY}, acts only on * <code>path</code>; if{@link SVNDepth#FILES}, resolves <code>path</code> * and its conflicted file children (if any); if {@link SVNDepth#IMMEDIATES} * , resolves <code>path</code> and all its immediate conflicted children * (both files and directories, if any); if {@link SVNDepth#INFINITY}, * resolves <code>path</code> and every conflicted file or directory * anywhere beneath it. * <p/> * If <code>conflictChoice</code> is {@link SVNConflictChoice#BASE}, * resolves the conflict with the old file contents; if * {@link SVNConflictChoice#MINE_FULL}, uses the original working contents; * if {@link SVNConflictChoice#THEIRS_FULL}, the new contents; and if * {@link SVNConflictChoice#MERGED}, doesn't change the contents at all, * just removes the conflict status, which is the pre-1.2 (pre-SVN 1.5) * behavior. * <p/> * {@link SVNConflictChoice#THEIRS_CONFLICT} and * {@link SVNConflictChoice#MINE_CONFLICT} are not legal for binary files or * properties. * <p/> * If <code>path</code> is not in a state of conflict to begin with, does * nothing. If <code>path</code>'s conflict state is removed and caller's * {@link ISVNEntryHandler} is not <span class="javakeyword">null</span>, * then an {@link SVNEventAction#RESOLVED} event is dispatched to the * handler. * <p/> * This is equivalent to calling * <code>doResolve(path, depth, true, true, conflictChoice)</code>. * * @param path * working copy path * @param depth * tree depth * @param conflictChoice * choice object for making decision while resolving * @throws SVNException * @since 1.2, SVN 1.5 */ public void doResolve(File path, SVNDepth depth, SVNConflictChoice conflictChoice) throws SVNException { doResolve(path, depth, true, true, conflictChoice); } /** * Performs automatic conflict resolution on a working copy * <code>path</code>. * <p/> * If <code>depth</code> is {@link SVNDepth#EMPTY}, acts only on * <code>path</code>; if{@link SVNDepth#FILES}, resolves <code>path</code> * and its conflicted file children (if any); if {@link SVNDepth#IMMEDIATES} * , resolves <code>path</code> and all its immediate conflicted children * (both files and directories, if any); if {@link SVNDepth#INFINITY}, * resolves <code>path</code> and every conflicted file or directory * anywhere beneath it. * <p/> * If <code>conflictChoice</code> is {@link SVNConflictChoice#BASE}, * resolves the conflict with the old file contents; if * {@link SVNConflictChoice#MINE_FULL}, uses the original working contents; * if {@link SVNConflictChoice#THEIRS_FULL}, the new contents; and if * {@link SVNConflictChoice#MERGED}, doesn't change the contents at all, * just removes the conflict status, which is the pre-1.2 (pre-SVN 1.5) * behavior. * <p/> * {@link SVNConflictChoice#THEIRS_CONFLICT} and * {@link SVNConflictChoice#MINE_CONFLICT} are not legal for binary files or * properties. * <p/> * If <code>path</code> is not in a state of conflict to begin with, does * nothing. If <code>path</code>'s conflict state is removed and caller's * {@link ISVNEntryHandler} is not <span class="javakeyword">null</span>, * then an {@link SVNEventAction#RESOLVED} event is dispatched to the * handler. * * @param path * working copy path * @param depth * tree depth * @param resolveContents * resolve content conflict * @param resolveProperties * resolve property conflict * @param conflictChoice * choice object for making decision while resolving * @throws SVNException * @since 1.2, SVN 1.5 */ public void doResolve(File path, SVNDepth depth, final boolean resolveContents, final boolean resolveProperties, SVNConflictChoice conflictChoice) throws SVNException { doResolve(path, depth, resolveContents, resolveProperties, true, conflictChoice); } /** * Performs automatic conflict resolution on a working copy * <code>path</code>. * <p/> * If <code>depth</code> is {@link SVNDepth#EMPTY}, acts only on * <code>path</code>; if{@link SVNDepth#FILES}, resolves <code>path</code> * and its conflicted file children (if any); if {@link SVNDepth#IMMEDIATES} * , resolves <code>path</code> and all its immediate conflicted children * (both files and directories, if any); if {@link SVNDepth#INFINITY}, * resolves <code>path</code> and every conflicted file or directory * anywhere beneath it. * <p/> * If <code>conflictChoice</code> is {@link SVNConflictChoice#BASE}, * resolves the conflict with the old file contents; if * {@link SVNConflictChoice#MINE_FULL}, uses the original working contents; * if {@link SVNConflictChoice#THEIRS_FULL}, the new contents; and if * {@link SVNConflictChoice#MERGED}, doesn't change the contents at all, * just removes the conflict status, which is the pre-1.2 (pre-SVN 1.5) * behavior. * <p/> * {@link SVNConflictChoice#THEIRS_CONFLICT} and * {@link SVNConflictChoice#MINE_CONFLICT} are not legal for binary files or * properties. * <p/> * If <code>path</code> is not in a state of conflict to begin with, does * nothing. If <code>path</code>'s conflict state is removed and caller's * {@link ISVNEntryHandler} is not <span class="javakeyword">null</span>, * then an {@link SVNEventAction#RESOLVED} event is dispatched to the * handler. * * @param path * working copy path * @param depth * tree depth * @param resolveContents * resolve content conflict * @param resolveProperties * resolve property conflict * @param resolveTree * n resolve any tree conlicts * @param conflictChoice * choice object for making decision while resolving * @throws SVNException * @since 1.3, SVN 1.6 */ public void doResolve(File path, SVNDepth depth, final boolean resolveContents, final boolean resolveProperties, final boolean resolveTree, SVNConflictChoice conflictChoice) throws SVNException { final SVNConflictChoice choice = conflictChoice == null ? SVNConflictChoice.MERGED : conflictChoice; path = path.getAbsoluteFile(); final SVNWCAccess wcAccess = createWCAccess(); int admLockLevel = SVNWCAccess.INFINITE_DEPTH; if (depth == SVNDepth.EMPTY || depth == SVNDepth.FILES) { admLockLevel = 0; } try { wcAccess.probeOpen(path, true, admLockLevel); if (!wcAccess.isWCRoot(path)) { wcAccess.close(); if (admLockLevel >= 0) { admLockLevel++; } wcAccess.probeOpen(path.getParentFile(), true, admLockLevel); } ISVNEntryHandler resolveEntryHandler = new ISVNEntryHandler() { public void handleEntry(File path, SVNEntry entry) throws SVNException { if (entry != null && entry.isDirectory() && !"".equals(entry.getName())) { return; } SVNNodeKind kind = SVNNodeKind.UNKNOWN; long revision = -1; boolean wcRoot = false; boolean resolved = false; if (entry != null && entry.isDirectory()) { wcRoot = wcAccess.isWCRoot(path); } if (resolveTree && !wcRoot) { File parentDir = path.getParentFile(); SVNAdminArea parentArea = wcAccess.probeRetrieve(parentDir); SVNTreeConflictDescription tc = parentArea.getTreeConflict(path.getName()); if (tc != null) { if (choice != SVNConflictChoice.MERGED) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CONFLICT_RESOLVER_FAILURE, "Tree conflicts can only be resolved to ''working'' state; ''{0}'' not resolved", path); SVNErrorManager.error(err, SVNLogType.WC); } parentArea.deleteTreeConflict(path.getName()); kind = tc.getNodeKind(); resolved = true; } } if (entry != null && (resolveContents || resolveProperties)) { kind = entry.getKind(); revision = entry.getRevision(); File conflictDir = entry.isDirectory() ? path : path.getParentFile(); SVNAdminArea conflictArea = wcAccess.retrieve(conflictDir); resolved |= conflictArea.markResolved(entry.getName(), resolveContents, resolveProperties, choice); } if (resolved) { SVNEvent event = SVNEventFactory.createSVNEvent(path, kind, null, revision, SVNEventAction.RESOLVED, null, null, null); dispatchEvent(event); } } public void handleError(File path, SVNErrorMessage error) throws SVNException { SVNErrorManager.error(error, SVNLogType.WC); } }; if (depth == SVNDepth.EMPTY) { SVNEntry entry = wcAccess.getEntry(path, false); if (entry != null) { resolveEntryHandler.handleEntry(path, entry); } else { SVNTreeConflictDescription tc = wcAccess.getTreeConflict(path); if (tc != null) { resolveEntryHandler.handleEntry(path, null); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", path); SVNErrorManager.error(err, SVNLogType.WC); } } } else { wcAccess.walkEntries(path, resolveEntryHandler, false, true, depth); } } finally { wcAccess.close(); } } /** * Locks file items in a Working Copy as well as in a repository so that no * other user can commit changes to them. * * @param paths * an array of local WC file paths that should be locked * @param stealLock * if <span class="javakeyword">true</span> then all existing * locks on the specified <code>paths</code> will be "stolen" * @param lockMessage * an optional lock comment * @throws SVNException * if one of the following is true: * <ul> * <li>a path to be locked is not under version control * <li>can not obtain a URL of a local path to lock it in the * repository - there's no such entry * <li><code>paths</code> to be locked belong to different * repositories * </ul> * @see #doLock(SVNURL[],boolean,String) */ public void doLock(File[] paths, boolean stealLock, String lockMessage) throws SVNException { if (paths == null || paths.length == 0) { return; } final Map entriesMap = new SVNHashMap(); Map pathsRevisionsMap = new SVNHashMap(); final SVNWCAccess wcAccess = createWCAccess(); try { final SVNURL topURL = collectLockInfo(wcAccess, paths, entriesMap, pathsRevisionsMap, true, stealLock); SVNRepository repository = createRepository(topURL, paths[0], wcAccess, true); final SVNURL rootURL = repository.getRepositoryRoot(true); repository.lock(pathsRevisionsMap, lockMessage, stealLock, new ISVNLockHandler() { public void handleLock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { SVNURL fullURL = rootURL.appendPath(path, false); LockInfo lockInfo = (LockInfo) entriesMap.get(fullURL); SVNAdminArea dir = wcAccess.probeRetrieve(lockInfo.myFile); if (error == null) { SVNEntry entry = wcAccess.getVersionedEntry(lockInfo.myFile, false); entry.setLockToken(lock.getID()); entry.setLockComment(lock.getComment()); entry.setLockOwner(lock.getOwner()); entry.setLockCreationDate(SVNDate.formatDate(lock.getCreationDate())); SVNVersionedProperties props = dir.getProperties(entry.getName()); if (props.getPropertyValue(SVNProperty.NEEDS_LOCK) != null) { SVNFileUtil.setReadonly(dir.getFile(entry.getName()), false); } SVNFileUtil.setExecutable(dir.getFile(entry.getName()), props.getPropertyValue(SVNProperty.EXECUTABLE) != null); dir.saveEntries(false); handleEvent(SVNEventFactory.createLockEvent(dir.getFile(entry.getName()), SVNEventAction.LOCKED, lock, null), ISVNEventHandler.UNKNOWN); } else { handleEvent(SVNEventFactory.createLockEvent(dir.getFile(lockInfo.myFile.getName()), SVNEventAction.LOCK_FAILED, lock, error), ISVNEventHandler.UNKNOWN); } } public void handleUnlock(String path, SVNLock lock, SVNErrorMessage error) { } }); } finally { wcAccess.close(); } } /** * Locks file items in a repository so that no other user can commit changes * to them. * * @param urls * an array of URLs to be locked * @param stealLock * if <span class="javakeyword">true</span> then all existing * locks on the specified <code>urls</code> will be "stolen" * @param lockMessage * an optional lock comment * @throws SVNException * @see #doLock(File[],boolean,String) */ public void doLock(SVNURL[] urls, boolean stealLock, String lockMessage) throws SVNException { Collection paths = new SVNHashSet(); SVNURL topURL = SVNURLUtil.condenceURLs(urls, paths, false); if (paths.isEmpty()) { paths.add(""); } Map pathsToRevisions = new SVNHashMap(); for (Iterator p = paths.iterator(); p.hasNext();) { String path = (String) p.next(); path = SVNEncodingUtil.uriDecode(path); pathsToRevisions.put(path, null); } checkCancelled(); SVNRepository repository = createRepository(topURL, null, null, true); repository.lock(pathsToRevisions, lockMessage, stealLock, new ISVNLockHandler() { public void handleLock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { if (error != null) { handleEvent(SVNEventFactory.createLockEvent(new File(path), SVNEventAction.LOCK_FAILED, lock, error), ISVNEventHandler.UNKNOWN); } else { handleEvent(SVNEventFactory.createLockEvent(new File(path), SVNEventAction.LOCKED, lock, null), ISVNEventHandler.UNKNOWN); } } public void handleUnlock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { } }); } /** * Unlocks file items in a Working Copy as well as in a repository. * * @param paths * an array of local WC file paths that should be unlocked * @param breakLock * if <span class="javakeyword">true</span> and there are locks * that belong to different users then those locks will be also * unlocked - that is "broken" * @throws SVNException * if one of the following is true: * <ul> * <li>a path is not under version control * <li>can not obtain a URL of a local path to unlock it in the * repository - there's no such entry * <li>if a path is not locked in the Working Copy and * <code>breakLock</code> is <span * class="javakeyword">false</span> * <li><code>paths</code> to be unlocked belong to different * repositories * </ul> * @see #doUnlock(SVNURL[],boolean) */ public void doUnlock(File[] paths, boolean breakLock) throws SVNException { if (paths == null || paths.length == 0) { return; } final Map entriesMap = new SVNHashMap(); Map pathsTokensMap = new SVNHashMap(); final SVNWCAccess wcAccess = createWCAccess(); try { final SVNURL topURL = collectLockInfo(wcAccess, paths, entriesMap, pathsTokensMap, false, breakLock); checkCancelled(); SVNRepository repository = createRepository(topURL, paths[0], wcAccess, true); final SVNURL rootURL = repository.getRepositoryRoot(true); repository.unlock(pathsTokensMap, breakLock, new ISVNLockHandler() { public void handleLock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { } public void handleUnlock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { SVNURL fullURL = rootURL.appendPath(path, false); LockInfo lockInfo = (LockInfo) entriesMap.get(fullURL); SVNEventAction action = null; SVNAdminArea dir = wcAccess.probeRetrieve(lockInfo.myFile); if (error == null || (error != null && error.getErrorCode() != SVNErrorCode.FS_LOCK_OWNER_MISMATCH)) { SVNEntry entry = wcAccess.getVersionedEntry(lockInfo.myFile, false); entry.setLockToken(null); entry.setLockComment(null); entry.setLockOwner(null); entry.setLockCreationDate(null); SVNVersionedProperties props = dir.getProperties(entry.getName()); if (props.getPropertyValue(SVNProperty.NEEDS_LOCK) != null) { SVNFileUtil.setReadonly(dir.getFile(entry.getName()), true); } dir.saveEntries(false); action = SVNEventAction.UNLOCKED; } if (error != null) { action = SVNEventAction.UNLOCK_FAILED; } if (action != null) { handleEvent(SVNEventFactory.createLockEvent(dir.getFile(lockInfo.myFile.getName()), action, lock, error), ISVNEventHandler.UNKNOWN); } } }); } finally { wcAccess.close(); } } /** * Unlocks file items in a repository. * * @param urls * an array of URLs that should be unlocked * @param breakLock * if <span class="javakeyword">true</span> and there are locks * that belong to different users then those locks will be also * unlocked - that is "broken" * @throws SVNException * @see #doUnlock(File[],boolean) */ public void doUnlock(SVNURL[] urls, boolean breakLock) throws SVNException { Collection paths = new SVNHashSet(); SVNURL topURL = SVNURLUtil.condenceURLs(urls, paths, false); if (paths.isEmpty()) { paths.add(""); } Map pathsToTokens = new SVNHashMap(); for (Iterator p = paths.iterator(); p.hasNext();) { String path = (String) p.next(); path = SVNEncodingUtil.uriDecode(path); pathsToTokens.put(path, null); } checkCancelled(); SVNRepository repository = createRepository(topURL, null, null, true); if (!breakLock) { pathsToTokens = fetchLockTokens(repository, pathsToTokens); } repository.unlock(pathsToTokens, breakLock, new ISVNLockHandler() { public void handleLock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { } public void handleUnlock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { if (error != null) { handleEvent(SVNEventFactory.createLockEvent(new File(path), SVNEventAction.UNLOCK_FAILED, null, error), ISVNEventHandler.UNKNOWN); } else { handleEvent(SVNEventFactory.createLockEvent(new File(path), SVNEventAction.UNLOCKED, null, null), ISVNEventHandler.UNKNOWN); } } }); } /** * Collects information about Working Copy item(s) and passes it to an info * handler. * <p/> * <p/> * If <code>revision</code> is valid and not local, then information will be * collected on remote items (that is taken from a repository). Otherwise * information is gathered on local items not accessing a repository. * * @param path * a WC item on which info should be obtained * @param revision * a target revision * @param recursive * <span class="javakeyword">true</span> to descend recursively * (relevant for directories) * @param handler * a caller's info handler * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> is not under version control <li>can * not obtain a URL corresponding to <code>path</code> to get * its information from the repository - there's no such entry * <li>if a remote info: <code>path</code> is an item that does * not exist in the specified <code>revision</code> * </ul> * @deprecated use * {@link #doInfo(File,SVNRevision,SVNRevision,SVNDepth,Collection,ISVNInfoHandler)} * instead */ public void doInfo(File path, SVNRevision revision, boolean recursive, ISVNInfoHandler handler) throws SVNException { doInfo(path, SVNRevision.UNDEFINED, revision, SVNDepth.getInfinityOrEmptyDepth(recursive), null, handler); } /** * Collects information about Working Copy item(s) and passes it to an info * handler. * <p/> * <p/> * If <code>revision</code> & <code>pegRevision</code> are valid and not * local, then information will be collected on remote items (that is taken * from a repository). Otherwise information is gathered on local items not * accessing a repository. * * @param path * a WC item on which info should be obtained * @param pegRevision * a revision in which <code>path</code> is first looked up * @param revision * a target revision * @param recursive * <span class="javakeyword">true</span> to descend recursively * (relevant for directories) * @param handler * a caller's info handler * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> is not under version control <li>can * not obtain a URL corresponding to <code>path</code> to get * its information from the repository - there's no such entry * <li>if a remote info: <code>path</code> is an item that does * not exist in the specified <code>revision</code> * </ul> * @deprecated use * {@link #doInfo(File,SVNRevision,SVNRevision,SVNDepth,Collection,ISVNInfoHandler)} * instead */ public void doInfo(File path, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNInfoHandler handler) throws SVNException { doInfo(path, pegRevision, revision, SVNDepth.getInfinityOrEmptyDepth(recursive), null, handler); } /** * Invokes <code>handler</code> to return information about * <code>path</code> in <code>revision</code>. The information returned is * system-generated metadata, not the sort of "property" metadata created by * users. See {@link SVNInfo}. * <p/> * If both revision arguments are either <span * class="javakeyword">null</span> or {@link SVNRevision#isLocal() local}, * or {@link SVNRevision#isValid() invalid}, then information will be pulled * solely from the working copy; no network connections will be made. * <p/> * Otherwise, information will be pulled from a repository. The actual node * revision selected is determined by the <code>path</code> as it exists in * <code>pegRevision</code>. If <code>pegRevision</code> is * {@link SVNRevision#UNDEFINED}, then it defaults to * {@link SVNRevision#WORKING}. * <p/> * If <code>path</code> is a file, just invokes <code>handler</code> on it. * If it is a directory, then descends according to <code>depth</code>. If * <code>depth</code> is{@link SVNDepth#EMPTY}, invokes <code>handler</code> * on <code>path</code> and nothing else; if {@link SVNDepth#FILES}, on * <code>path</code> and its immediate file children; if * {@link SVNDepth#IMMEDIATES}, the preceding plus on each immediate * subdirectory; if {@link SVNDepth#INFINITY}, then recurses fully, invoking * <code>handler</code> on <code>path</code> and everything beneath it. * <p/> * <code>changeLists</code> is a collection of <code>String</code> * changelist names, used as a restrictive filter on items whose info is * reported; that is, doesn't report info about any item unless it's a * member of one of those changelists. If <code>changeLists</code> is empty * (or <span class="javakeyword">null</span>), no changelist filtering * occurs. * * @param path * a WC item on which info should be obtained * @param pegRevision * a revision in which <code>path</code> is first looked up * @param revision * a target revision * @param depth * tree depth * @param changeLists * collection changelist names * @param handler * caller's info handler * @throws SVNException * @since 1.2, SVN 1.5 */ public void doInfo(File path, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, Collection changeLists, ISVNInfoHandler handler) throws SVNException { if (handler == null) { return; } boolean local = (revision == null || !revision.isValid() || revision.isLocal()) && (pegRevision == null || !pegRevision.isValid() || pegRevision.isLocal()); if (!local) { SVNWCAccess wcAccess = createWCAccess(); SVNRevision wcRevision = null; SVNURL url = null; try { wcAccess.probeOpen(path, false, 0); SVNEntry entry = wcAccess.getVersionedEntry(path, false); url = entry.getSVNURL(); if (url == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", path); SVNErrorManager.error(err, SVNLogType.WC); } wcRevision = SVNRevision.create(entry.getRevision()); } finally { wcAccess.close(); } doInfo(url, pegRevision == null || !pegRevision.isValid() || pegRevision.isLocal() ? wcRevision : pegRevision, revision, depth, handler); return; } Collection changelistsSet = null; if (changeLists != null) { changelistsSet = new SVNHashSet(); for (Iterator changeListsIter = changeLists.iterator(); changeListsIter.hasNext();) { String changeList = (String) changeListsIter.next(); changelistsSet.add(changeList); } } crawlEntries(path, depth, changelistsSet, handler); } /** * Collects information about item(s) in a repository and passes it to an * info handler. * * @param url * a URL of an item which information is to be obtained and * processed * @param pegRevision * a revision in which the item is first looked up * @param revision * a target revision * @param recursive * <span class="javakeyword">true</span> to descend recursively * (relevant for directories) * @param handler * a caller's info handler * @throws SVNException * if <code>url</code> is an item that does not exist in the * specified <code>revision</code> * @deprecated use * {@link #doInfo(SVNURL,SVNRevision,SVNRevision,SVNDepth,ISVNInfoHandler)} * instead */ public void doInfo(SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNInfoHandler handler) throws SVNException { doInfo(url, pegRevision, revision, SVNDepth.getInfinityOrEmptyDepth(recursive), handler); } /** * Invokes <code>handler</code> to return information about <code>url</code> * in <code>revision</code>. The information returned is system-generated * metadata, not the sort of "property" metadata created by users. See * {@link SVNInfo}. * <p/> * If <code>revision</code> argument is either <span * class="javakeyword">null</span> or {@link SVNRevision#isValid() invalid}, * it defaults to {@link SVNRevision#HEAD}. If <code>revision</code> is * {@link SVNRevision#PREVIOUS} (or some other kind that requires a local * path), an error will be returned, because the desired revision cannot be * determined. If <code>pegRevision</code> argument is either <span * class="javakeyword">null</span> or {@link SVNRevision#isValid() invalid}, * it defaults to <code>revision</code>. * <p/> * Information will be pulled from the repository. The actual node revision * selected is determined by the <code>url</code> as it exists in * <code>pegRevision</code>. If <code>pegRevision</code> is * {@link SVNRevision#UNDEFINED}, then it defaults to * {@link SVNRevision#WORKING}. * <p/> * If <code>url</code> is a file, just invokes <code>handler</code> on it. * If it is a directory, then descends according to <code>depth</code>. If * <code>depth</code> is{@link SVNDepth#EMPTY}, invokes <code>handler</code> * on <code>url</code> and nothing else; if {@link SVNDepth#FILES}, on * <code>url</code> and its immediate file children; if * {@link SVNDepth#IMMEDIATES}, the preceding plus on each immediate * subdirectory; if {@link SVNDepth#INFINITY}, then recurses fully, invoking * <code>handler</code> on <code>url</code> and everything beneath it. * * @param url * versioned item url * @param pegRevision * revision in which <code>path</code> is first looked up * @param revision * target revision * @param depth * tree depth * @param handler * caller's info handler * @throws SVNException * @since 1.2, SVN 1.5 */ public void doInfo(SVNURL url, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, ISVNInfoHandler handler) throws SVNException { depth = depth == null ? SVNDepth.UNKNOWN : depth; long[] revNum = { SVNRepository.INVALID_REVISION }; SVNRepository repos = createRepository(url, null, null, pegRevision, revision, revNum); url = repos.getLocation(); SVNDirEntry rootEntry = null; SVNURL reposRoot = repos.getRepositoryRoot(true); String reposUUID = repos.getRepositoryUUID(true); String baseName = SVNPathUtil.tail(url.getPath()); try { rootEntry = repos.info("", revNum[0]); } catch (SVNException e) { if (e.getErrorMessage() != null && e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED) { if (url.equals(reposRoot)) { if (depth.compareTo(SVNDepth.EMPTY) > 0) { SVNLock[] locks = null; if (pegRevision == SVNRevision.HEAD) { try { locks = repos.getLocks(""); } catch (SVNException svne) { SVNErrorCode code = svne.getErrorMessage().getErrorCode(); if (code == SVNErrorCode.RA_NOT_IMPLEMENTED || code == SVNErrorCode.UNSUPPORTED_FEATURE) { locks = new SVNLock[0]; } else { throw svne; } } } else { locks = new SVNLock[0]; } locks = locks == null ? new SVNLock[0] : locks; Map locksMap = new SVNHashMap(); for (int i = 0; i < locks.length; i++) { SVNLock lock = locks[i]; locksMap.put(lock.getPath(), lock); } pushDirInfo(repos, SVNRevision.create(revNum[0]), "", repos.getRepositoryRoot(true), reposUUID, url, locksMap, depth, handler); return; } SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Server does not support retrieving information about the repository root"); SVNErrorManager.error(err, SVNLogType.WC); } SVNNodeKind urlKind = repos.checkPath("", revNum[0]); if (urlKind == SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' non-existent in revision {1}", new Object[] { url, new Long(revNum[0]) }); SVNErrorManager.error(err, SVNLogType.WC); } SVNRepository parentRepos = createRepository(url.removePathTail(), null, null, false); Collection dirEntries = parentRepos.getDir("", revNum[0], null, SVNDirEntry.DIRENT_KIND | SVNDirEntry.DIRENT_CREATED_REVISION | SVNDirEntry.DIRENT_TIME | SVNDirEntry.DIRENT_LAST_AUTHOR, (Collection) null); for (Iterator ents = dirEntries.iterator(); ents.hasNext();) { SVNDirEntry dirEntry = (SVNDirEntry) ents.next(); if (baseName.equals(dirEntry.getName())) { rootEntry = dirEntry; break; } } if (rootEntry == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' non-existent in revision {1}", new Object[] { url, new Long(revNum[0]) }); SVNErrorManager.error(err, SVNLogType.WC); } } else { throw e; } } if (rootEntry == null || rootEntry.getKind() == SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' non-existent in revision ''{1}''", new Object[] { url, new Long(revNum[0]) }); SVNErrorManager.error(err, SVNLogType.WC); } SVNLock lock = null; if (rootEntry.getKind() == SVNNodeKind.FILE) { try { SVNRepositoryLocation[] locations = getLocations(url, null, null, SVNRevision.create(revNum[0]), SVNRevision.HEAD, SVNRevision.UNDEFINED); if (locations != null && locations.length > 0) { SVNURL headURL = locations[0].getURL(); if (headURL.equals(url)) { try { lock = repos.getLock(""); } catch (SVNException e) { if (!(e.getErrorMessage() != null && e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED)) { throw e; } } } } } catch (SVNException e) { SVNErrorCode code = e.getErrorMessage().getErrorCode(); if (code != SVNErrorCode.FS_NOT_FOUND && code != SVNErrorCode.CLIENT_UNRELATED_RESOURCES) { throw e; } } } SVNInfo info = SVNInfo.createInfo(baseName, reposRoot, reposUUID, url, SVNRevision.create(revNum[0]), rootEntry, lock); handler.handleInfo(info); if (depth.compareTo(SVNDepth.EMPTY) > 0 && rootEntry.getKind() == SVNNodeKind.DIR) { SVNLock[] locks = null; if (pegRevision == SVNRevision.HEAD) { try { locks = repos.getLocks(""); } catch (SVNException svne) { SVNErrorCode code = svne.getErrorMessage().getErrorCode(); if (code == SVNErrorCode.RA_NOT_IMPLEMENTED || code == SVNErrorCode.UNSUPPORTED_FEATURE) { locks = new SVNLock[0]; } else { throw svne; } } } else { locks = new SVNLock[0]; } locks = locks == null ? new SVNLock[0] : locks; Map locksMap = new SVNHashMap(); for (int i = 0; i < locks.length; i++) { lock = locks[i]; locksMap.put(lock.getPath(), lock); } pushDirInfo(repos, SVNRevision.create(revNum[0]), "", repos.getRepositoryRoot(true), reposUUID, url, locksMap, depth, handler); } } /** * Returns the current Working Copy min- and max- revisions as well as * changes and switch status within a single string. * <p/> * This method is the same as * <code>doGetWorkingCopyID(path, trailURL, false)</code>. * * @param path * a local path * @param trailURL * optional: if not <span class="javakeyword">null</span> * specifies the name of the item that should be met in the URL * corresponding to the repository location of the * <code>path</code>; if that URL ends with something different * than this optional parameter - the Working Copy will be * considered "switched" * @return brief info on the Working Copy or the string "exported" if * <code>path</code> is a clean directory * @throws SVNException * if <code>path</code> is neither versioned nor even exported * @see #doGetWorkingCopyID(File,String,boolean) */ public String doGetWorkingCopyID(final File path, String trailURL) throws SVNException { return doGetWorkingCopyID(path, trailURL, false); } /** * Returns the current Working Copy min- and max- revisions as well as * changes and switch status within a single string. * <p/> * <p/> * A return string has a form of <code>"minR[:maxR][M][S]"</code> where: * <ul> * <li><code>minR</code> - is the smallest revision number met in the * Working Copy * <li><code>maxR</code> - is the biggest revision number met in the Working * Copy; appears only if there are different revision in the Working Copy * <li><code>M</code> - appears only if there're local edits to the Working * Copy - that means 'Modified' * <li><code>S</code> - appears only if the Working Copy is switched against * a different URL * </ul> * If <code>path</code> is a directory - this method recursively descends * into the Working Copy, collects and processes local information. * <p/> * This method operates on local working copies only without accessing a * repository. * * @param path * a local path * @param trailURL * optional: if not <span class="javakeyword">null</span> * specifies the name of the item that should be met in the URL * corresponding to the repository location of the * <code>path</code>; if that URL ends with something different * than this optional parameter - the Working Copy will be * considered "switched" * @param committed * if <span class="javakeyword">true</span> committed (last * chaned) revisions instead of working copy ones are reported * @return brief info on the Working Copy or the string "exported" if * <code>path</code> is a clean directory * @throws SVNException * if <code>path</code> is neither versioned nor even exported * @since 1.2 */ public String doGetWorkingCopyID(final File path, String trailURL, final boolean committed) throws SVNException { SVNWCAccess wcAccess = createWCAccess(); try { wcAccess.open(path, false, 0); } catch (SVNException e) { SVNFileType pathType = SVNFileType.getType(path); if (pathType == SVNFileType.DIRECTORY) { return "exported"; } else if (pathType == SVNFileType.NONE) { throw e; } return "'" + path + "' is not versioned and not exported"; } finally { wcAccess.close(); } SVNStatusClient16 statusClient = new SVNStatusClient16((ISVNAuthenticationManager) null, getOptions()); statusClient.setIgnoreExternals(true); final long[] maxRevision = new long[1]; final long[] minRevision = new long[] { -1 }; final boolean[] switched = new boolean[3]; final String[] wcURL = new String[1]; statusClient.doStatus(path, SVNRevision.WORKING, SVNDepth.INFINITY, false, true, false, false, new ISVNStatusHandler() { public void handleStatus(SVNStatus status) { if (status.getEntryProperties() == null || status.getEntryProperties().isEmpty()) { return; } if (status.getContentsStatus() != SVNStatusType.STATUS_ADDED && !status.isFileExternal()) { SVNRevision revision = committed ? status.getCommittedRevision() : status.getRevision(); if (revision != null) { if (minRevision[0] < 0 || minRevision[0] > revision.getNumber()) { minRevision[0] = revision.getNumber(); } maxRevision[0] = Math.max(maxRevision[0], revision.getNumber()); } } switched[0] |= status.isSwitched(); switched[1] |= status.getContentsStatus() != SVNStatusType.STATUS_NORMAL; switched[1] |= status.getPropertiesStatus() != SVNStatusType.STATUS_NORMAL && status.getPropertiesStatus() != SVNStatusType.STATUS_NONE; switched[2] |= status.getEntry() != null && status.getEntry().getDepth() != SVNDepth.INFINITY; if (wcURL[0] == null && status.getFile() != null && status.getFile().equals(path) && status.getURL() != null) { wcURL[0] = status.getURL().toString(); } } }, null); if (!switched[0] && trailURL != null) { if (wcURL[0] == null) { switched[0] = true; } else { switched[0] = !wcURL[0].endsWith(trailURL); } } StringBuffer id = new StringBuffer(); id.append(minRevision[0]); if (minRevision[0] != maxRevision[0]) { id.append(":").append(maxRevision[0]); } if (switched[1]) { id.append("M"); } if (switched[0]) { id.append("S"); } if (switched[2]) { id.append("P"); } return id.toString(); } /** * Collects and returns information on a single Working Copy item. * <p/> * This method is the same as * <code>doInfo(path, SVNRevision.UNDEFINED, revision, SVNDepth.EMPTY, null, handler)</code> * where <code>handler</code> just stores {@link SVNInfo} for the * <code>path</code> and then returns it to the caller. * * @param path * a WC item on which info should be obtained * @param revision * a target revision * @return collected info * @throws SVNException * if one of the following is true: * <ul> * <li><code>path</code> is not under version control <li>can * not obtain a URL corresponding to <code>path</code> to get * its information from the repository - there's no such entry * <li>if a remote info: <code>path</code> is an item that does * not exist in the specified <code>revision</code> * </ul> * @see #doInfo(File,SVNRevision,SVNRevision,SVNDepth,Collection,ISVNInfoHandler) */ public SVNInfo doInfo(File path, SVNRevision revision) throws SVNException { final SVNInfo[] result = new SVNInfo[1]; doInfo(path, SVNRevision.UNDEFINED, revision, SVNDepth.EMPTY, null, new ISVNInfoHandler() { public void handleInfo(SVNInfo info) { if (result[0] == null) { result[0] = info; } } }); return result[0]; } /** * Collects and returns information on a single item in a repository. * <p/> * This method is the same as * <code>doInfo(url, pegRevision, revision, SVNDepth.EMPTY, handler)</code> * where <code>handler</code> just stores {@link SVNInfo} for the * <code>url</code>. * * @param url * a URL of an item which information is to be obtained * @param pegRevision * a revision in which the item is first looked up * @param revision * a target revision * @return collected info * @throws SVNException * if <code>url</code> is an item that does not exist in the * specified <code>revision</code> * @see #doInfo(SVNURL,SVNRevision,SVNRevision,SVNDepth,ISVNInfoHandler) */ public SVNInfo doInfo(SVNURL url, SVNRevision pegRevision, SVNRevision revision) throws SVNException { final SVNInfo[] result = new SVNInfo[1]; doInfo(url, pegRevision, revision, SVNDepth.EMPTY, new ISVNInfoHandler() { public void handleInfo(SVNInfo info) { if (result[0] == null) { result[0] = info; } } }); return result[0]; } /** * Recursively removes all DAV-specific <span * class="javakeyword">"svn:wc:"</span> properties from the * <code>directory</code> and beneath. * <p> * This method does not connect to a repository, it's a local operation * only. Nor does it change any user's versioned data. Changes are made only * in administrative version control files. * * @param directory * working copy path * @throws SVNException * @since 1.2 */ public void doCleanupWCProperties(File directory) throws SVNException { SVNWCAccess wcAccess = SVNWCAccess.newInstance(this); try { SVNAdminArea dir = wcAccess.open(directory, true, true, -1); if (dir != null) { SVNPropertiesManager.deleteWCProperties(dir, null, true); } } finally { wcAccess.close(); } } /** * Changes working copy format. This method may be used to upgrade\downgrade * working copy formats. * <p> * If externals are not {@link SVNBasicClient#isIgnoreExternals() ignored} * then external working copies are also converted to the new working copy * <code>format</code>. * <p> * This method does not connect to a repository, it's a local operation * only. Nor does it change any user's versioned data. Changes are made only * in administrative version control files. * * @param directory * working copy directory * @param format * format to set, supported formats are: 9 (SVN 1.5), 8 (SVN 1.4) * and 4 (SVN 1.2) * @throws SVNException * @since 1.2 */ public void doSetWCFormat(File directory, int format) throws SVNException { SVNAdminAreaInfo info = null; SVNWCAccess wcAccess = SVNWCAccess.newInstance(this); try { info = wcAccess.openAnchor(directory, false, -1); setWCFormat(info, info.getTarget(), format); } finally { wcAccess.close(); } if (!isIgnoreExternals() && info != null) { Collection processedDirs = new SVNHashSet(); Map externals = info.getOldExternals(); for (Iterator paths = externals.keySet().iterator(); paths.hasNext();) { String path = (String) paths.next(); String value = (String) externals.get(path); if (value == null) { continue; } SVNExternal[] externalDefs = SVNExternal.parseExternals("", value); for (int i = 0; i < externalDefs.length; i++) { String externalPath = externalDefs[i].getPath(); File externalDir = new File(info.getAnchor().getRoot(), SVNPathUtil.append(path, externalPath)); if (processedDirs.add(externalDir)) { try { wcAccess.open(externalDir, false, 0); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) { continue; } throw svne; } finally { wcAccess.close(); } try { doSetWCFormat(externalDir, format); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) { continue; } throw e; } } } } externals = info.getNewExternals(); for (Iterator paths = externals.keySet().iterator(); paths.hasNext();) { String path = (String) paths.next(); String value = (String) externals.get(path); if (value == null) { continue; } SVNExternal[] externalDefs = SVNExternal.parseExternals("", value); for (int i = 0; i < externalDefs.length; i++) { String externalPath = externalDefs[i].getPath(); File externalDir = new File(info.getAnchor().getRoot(), SVNPathUtil.append(path, externalPath)); if (processedDirs.add(externalDir)) { try { wcAccess.open(externalDir, false, 0); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) { continue; } throw svne; } finally { wcAccess.close(); } try { doSetWCFormat(externalDir, format); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) { continue; } throw e; } } } } } } /** * This method is deprecated. * * @param path * a WC item which properties are to be modified * @param propName * a property name * @param propValue * a property value * @param force * <span class="javakeyword">true</span> to force the operation * to run * @param recursive * <span class="javakeyword">true</span> to descend recursively * @param handler * a caller's property handler * @throws SVNException * if one of the following is true: * <ul> * <li><code>propName</code> is a revision property * <li><code>propName</code> starts with the * {@link org.tmatesoft.svn.core.SVNProperty#SVN_WC_PREFIXsvn * :wc:} prefix * </ul> * @deprecated use * {@link #doSetProperty(File,String,SVNPropertyValue,boolean,SVNDepth,ISVNPropertyHandler,Collection)} * instead */ public void doSetProperty(File path, String propName, SVNPropertyValue propValue, boolean force, boolean recursive, ISVNPropertyHandler handler) throws SVNException { doSetProperty(path, propName, propValue, force, SVNDepth.getInfinityOrEmptyDepth(recursive), handler, null); } private SVNAdminArea addParentDirectories(SVNWCAccess wcAccess, File path) throws SVNException { try { return wcAccess.open(path, true, 0); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) { if (path.getParentFile() == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_NO_VERSIONED_PARENT); SVNErrorManager.error(err, SVNLogType.WC); } else if (SVNFileUtil.getAdminDirectoryName().equals(path.getName())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RESERVED_FILENAME_SPECIFIED, "''{0}'' ends in a reserved name", path); SVNErrorManager.error(err, SVNLogType.WC); } else { File parentPath = path.getParentFile(); SVNAdminArea parentDir = addParentDirectories(wcAccess, parentPath); SVNWCManager.add(path, parentDir, null, SVNRevision.UNDEFINED, SVNDepth.INFINITY); return wcAccess.getAdminArea(path); } } throw e; } } private void doGetRevisionProperty(SVNRepository repos, String propName, long revNumber, ISVNPropertyHandler handler) throws SVNException { if (propName != null) { SVNPropertyValue value = repos.getRevisionPropertyValue(revNumber, propName); if (value != null) { handler.handleProperty(revNumber, new SVNPropertyData(propName, value, getOptions())); } } else { SVNProperties props = new SVNProperties(); repos.getRevisionProperties(revNumber, props); for (Iterator names = props.nameSet().iterator(); names.hasNext();) { String name = (String) names.next(); SVNPropertyValue value = props.getSVNPropertyValue(name); handler.handleProperty(revNumber, new SVNPropertyData(name, value, getOptions())); } } } private void addDirectory(File wcRoot, File path, SVNAdminArea parentDir, boolean force, boolean noIgnore, SVNDepth depth, boolean setDepth) throws SVNException { checkCancelled(); try { SVNWCManager.add(path, parentDir, null, SVNRevision.UNDEFINED, setDepth ? SVNDepth.INFINITY : null); } catch (SVNException e) { if (!(force && e.getErrorMessage().getErrorCode() == SVNErrorCode.ENTRY_EXISTS)) { throw e; } } SVNWCAccess access = parentDir.getWCAccess(); SVNAdminArea dir = access.retrieve(path); Collection ignores = Collections.EMPTY_SET; if (!noIgnore) { ignores = SVNStatusEditor.getIgnorePatterns(dir, SVNStatusEditor.getGlobalIgnores(getOptions())); } String relativePath = SVNPathUtil.getRelativePath(wcRoot.getAbsolutePath().replace(File.separatorChar, '/'), dir.getRoot().getAbsolutePath().replace(File.separatorChar, '/')); relativePath = relativePath != null ? "/" + relativePath : null; File[] children = SVNFileListUtil.listFiles(dir.getRoot()); for (int i = 0; children != null && i < children.length; i++) { checkCancelled(); if (SVNFileUtil.getAdminDirectoryName().equals(children[i].getName())) { continue; } if (!noIgnore) { String rootRelativePath = relativePath != null ? SVNPathUtil.append(relativePath, children[i].getName()) : null; if (SVNStatusEditor.isIgnored(ignores, children[i], rootRelativePath)) { continue; } } SVNFileType childType = SVNFileType.getType(children[i]); if (childType == SVNFileType.DIRECTORY && depth.compareTo(SVNDepth.IMMEDIATES) >= 0) { SVNDepth depthBelowHere = depth; if (depth == SVNDepth.IMMEDIATES) { depthBelowHere = SVNDepth.EMPTY; } addDirectory(wcRoot, children[i], dir, force, noIgnore, depthBelowHere, setDepth); } else if (childType != SVNFileType.UNKNOWN && childType != SVNFileType.DIRECTORY && depth.compareTo(SVNDepth.FILES) >= 0) { try { addFile(children[i], childType, dir); } catch (SVNException e) { if (force && e.getErrorMessage().getErrorCode() == SVNErrorCode.ENTRY_EXISTS) { continue; } throw e; } } } } private void addFile(File path, SVNFileType type, SVNAdminArea dir) throws SVNException { ISVNEventHandler handler = dir.getWCAccess().getEventHandler(); dir.getWCAccess().setEventHandler(null); SVNWCManager.add(path, dir, null, SVNRevision.UNDEFINED, null); dir.getWCAccess().setEventHandler(handler); String mimeType = null; if (type == SVNFileType.SYMLINK) { SVNPropertiesManager.setProperty(dir.getWCAccess(), path, SVNProperty.SPECIAL, SVNProperty.getValueOfBooleanProperty(SVNProperty.SPECIAL), false); } else { Map props = SVNPropertiesManager.computeAutoProperties(getOptions(), path, null); for (Iterator names = props.keySet().iterator(); names.hasNext();) { String propName = (String) names.next(); String propValue = (String) props.get(propName); try { SVNPropertiesManager.setProperty(dir.getWCAccess(), path, propName, SVNPropertyValue.create(propValue), false); } catch (SVNException e) { if (SVNProperty.EOL_STYLE.equals(propName) && e.getErrorMessage().getErrorCode() == SVNErrorCode.ILLEGAL_TARGET && e.getErrorMessage().getMessage().indexOf("newlines") >= 0) { ISVNAddParameters.Action action = getAddParameters().onInconsistentEOLs(path); if (action == ISVNAddParameters.REPORT_ERROR) { ISVNEventHandler eventHandler = getEventDispatcher(); try { setEventHandler(null); doRevert(path, dir, SVNDepth.EMPTY, false, null); } catch (SVNException svne) { } finally { setEventHandler(eventHandler); } throw e; } else if (action == ISVNAddParameters.ADD_AS_IS) { SVNPropertiesManager.setProperty(dir.getWCAccess(), path, propName, null, false); } else if (action == ISVNAddParameters.ADD_AS_BINARY) { SVNPropertiesManager.setProperty(dir.getWCAccess(), path, propName, null, false); mimeType = SVNFileUtil.BINARY_MIME_TYPE; } } else { ISVNEventHandler eventHandler = getEventDispatcher(); try { setEventHandler(null); doRevert(path, dir, SVNDepth.EMPTY, false, null); } catch (SVNException svne) { } finally { setEventHandler(eventHandler); } throw e; } } } if (mimeType != null) { SVNPropertiesManager.setProperty(dir.getWCAccess(), path, SVNProperty.MIME_TYPE, SVNPropertyValue.create(mimeType), false); } else { mimeType = (String) props.get(SVNProperty.MIME_TYPE); } } SVNEvent event = SVNEventFactory.createSVNEvent(dir.getFile(path.getName()), SVNNodeKind.FILE, mimeType, SVNRepository.INVALID_REVISION, SVNEventAction.ADD, null, null, null); dispatchEvent(event); } private SVNURL collectLockInfo(SVNWCAccess wcAccess, File[] files, Map lockInfo, Map lockPaths, boolean lock, boolean stealLock) throws SVNException { String[] paths = new String[files.length]; for (int i = 0; i < files.length; i++) { paths[i] = files[i].getAbsolutePath(); paths[i] = paths[i].replace(File.separatorChar, '/'); } Collection condencedPaths = new ArrayList(); String commonParentPath = SVNPathUtil.condencePaths(paths, condencedPaths, false); if (condencedPaths.isEmpty()) { condencedPaths.add(SVNPathUtil.tail(commonParentPath)); commonParentPath = SVNPathUtil.removeTail(commonParentPath); } if (commonParentPath == null || "".equals(commonParentPath)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "No common parent found, unable to operate on dijoint arguments"); SVNErrorManager.error(err, SVNLogType.WC); } paths = (String[]) condencedPaths.toArray(new String[condencedPaths.size()]); int depth = 0; for (int i = 0; i < paths.length; i++) { int segments = SVNPathUtil.getSegmentsCount(paths[i]); if (depth < segments) { depth = segments; } } wcAccess.probeOpen(new File(commonParentPath).getAbsoluteFile(), true, depth); for (int i = 0; i < paths.length; i++) { File file = new File(commonParentPath, paths[i]); SVNEntry entry = wcAccess.getVersionedEntry(file, false); if (entry.getURL() == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", file); SVNErrorManager.error(err, SVNLogType.WC); } if (lock) { SVNRevision revision = stealLock ? SVNRevision.UNDEFINED : SVNRevision.create(entry.getRevision()); lockInfo.put(entry.getSVNURL(), new LockInfo(file, revision)); } else { if (!stealLock && entry.getLockToken() == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_MISSING_LOCK_TOKEN, "''{0}'' is not locked in this working copy", file); SVNErrorManager.error(err, SVNLogType.WC); } lockInfo.put(entry.getSVNURL(), new LockInfo(file, stealLock ? null : entry.getLockToken())); } } checkCancelled(); SVNURL[] urls = (SVNURL[]) lockInfo.keySet().toArray(new SVNURL[lockInfo.size()]); Collection urlPaths = new SVNHashSet(); final SVNURL topURL = SVNURLUtil.condenceURLs(urls, urlPaths, false); if (urlPaths.isEmpty()) { urlPaths.add(""); } if (topURL == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Unable to lock/unlock across multiple repositories"); SVNErrorManager.error(err, SVNLogType.WC); } for (Iterator encodedPaths = urlPaths.iterator(); encodedPaths.hasNext();) { String encodedPath = (String) encodedPaths.next(); SVNURL fullURL = topURL.appendPath(encodedPath, true); LockInfo info = (LockInfo) lockInfo.get(fullURL); encodedPath = SVNEncodingUtil.uriDecode(encodedPath); if (lock) { if (info.myRevision == SVNRevision.UNDEFINED) { lockPaths.put(encodedPath, null); } else { lockPaths.put(encodedPath, new Long(info.myRevision.getNumber())); } } else { lockPaths.put(encodedPath, info.myToken); } } return topURL; } private void crawlEntries(File path, SVNDepth depth, final Collection changeLists, final ISVNInfoHandler handler) throws SVNException { final SVNWCAccess wcAccess = createWCAccess(); int admLockLevel = getLevelsToLockFromDepth(depth); try { wcAccess.probeOpen(path, false, admLockLevel); wcAccess.walkEntries(path, new ISVNEntryHandler() { public void handleEntry(File path, SVNEntry entry) throws SVNException { if (entry.isDirectory() && !entry.isThisDir()) { return; } if (SVNWCAccess.matchesChangeList(changeLists, entry)) { reportEntry(path, entry, handler); } } public void handleError(File path, SVNErrorMessage error) throws SVNException { if (error != null && error.getErrorCode() == SVNErrorCode.UNVERSIONED_RESOURCE) { SVNAdminArea dir = wcAccess.probeTry(path.getParentFile(), false, 0); if (dir != null) { SVNTreeConflictDescription tc = dir.getTreeConflict(path.getName()); if (tc != null) { SVNInfo info = SVNInfo.createInfo(path, tc); handler.handleInfo(info); return; } } } SVNErrorManager.error(error, SVNLogType.WC); } }, false, depth); } finally { wcAccess.close(); } } private void reportEntry(File path, SVNEntry entry, ISVNInfoHandler handler) throws SVNException { if (entry.isDirectory() && !"".equals(entry.getName())) { return; } handler.handleInfo(SVNInfo.createInfo(path, entry)); } private boolean doRevert(File path, SVNAdminArea parent, SVNDepth depth, boolean useCommitTimes, Collection changeLists) throws SVNException { checkCancelled(); SVNWCAccess wcAccess = parent.getWCAccess(); SVNAdminArea dir = wcAccess.probeRetrieve(path); SVNEntry entry = wcAccess.getEntry(path, false); SVNTreeConflictDescription treeConflict = wcAccess.getTreeConflict(path); if (entry == null && treeConflict == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNVERSIONED_RESOURCE, "Cannot revert unversioned item ''{0}''", path); SVNErrorManager.error(err, SVNLogType.WC); } if (entry != null && entry.getKind() == SVNNodeKind.DIR) { SVNFileType fileType = SVNFileType.getType(path); if (fileType != SVNFileType.DIRECTORY && !entry.isScheduledForAddition()) { if (isRevertMissingDirectories() && entry.getSchedule() != null && !entry.isThisDir()) { boolean reverted = revert(parent, entry.getName(), entry, useCommitTimes); if (reverted) { SVNEvent event = SVNEventFactory.createSVNEvent(dir.getFile(entry.getName()), entry.getKind(), null, entry.getRevision(), SVNEventAction.REVERT, null, null, null); dispatchEvent(event); } return reverted; } SVNEvent event = SVNEventFactory.createSVNEvent(dir.getFile(entry.getName()), entry.getKind(), null, entry.getRevision(), SVNEventAction.FAILED_REVERT, null, null, null); dispatchEvent(event); return false; } } if (entry != null && entry.getKind() != SVNNodeKind.DIR && entry.getKind() != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Cannot revert ''{0}'': unsupported entry node kind", path); SVNErrorManager.error(err, SVNLogType.WC); } SVNFileType fileType = SVNFileType.getType(path); if (fileType == SVNFileType.UNKNOWN) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Cannot revert ''{0}'': unsupported node kind in working copy", path); SVNErrorManager.error(err, SVNLogType.WC); } boolean reverted = false; if (SVNWCAccess.matchesChangeList(changeLists, entry)) { if (treeConflict != null) { parent.deleteTreeConflict(path.getName()); reverted = true; } if (entry != null) { if (entry.isScheduledForAddition()) { boolean wasDeleted = false; if (entry.getKind() == SVNNodeKind.FILE) { wasDeleted = entry.isDeleted(); parent.removeFromRevisionControl(path.getName(), false, false); } else if (entry.getKind() == SVNNodeKind.DIR) { SVNEntry entryInParent = parent.getEntry(path.getName(), true); if (entryInParent != null) { wasDeleted = entryInParent.isDeleted(); } if (fileType == SVNFileType.NONE || wcAccess.isMissing(path)) { parent.deleteEntry(path.getName()); parent.saveEntries(false); } else { dir.removeFromRevisionControl("", false, false); } } reverted = true; depth = SVNDepth.EMPTY; if (wasDeleted) { Map attributes = new SVNHashMap(); attributes.put(SVNProperty.KIND, entry.getKind().toString()); attributes.put(SVNProperty.DELETED, Boolean.TRUE.toString()); parent.modifyEntry(path.getName(), attributes, true, false); } } else if (entry.getSchedule() == null || entry.isScheduledForDeletion() || entry.isScheduledForReplacement()) { if (entry.getKind() == SVNNodeKind.FILE) { reverted = revert(parent, entry.getName(), entry, useCommitTimes); } else if (entry.getKind() == SVNNodeKind.DIR) { reverted = revert(dir, dir.getThisDirName(), entry, useCommitTimes); if (reverted && parent != dir) { SVNEntry entryInParent = parent.getEntry(path.getName(), false); revert(parent, path.getName(), entryInParent, useCommitTimes); } if (entry.isScheduledForReplacement()) { depth = SVNDepth.INFINITY; } } } } if (reverted) { SVNEvent event = null; if (entry != null) { event = SVNEventFactory.createSVNEvent(dir.getFile(entry.getName()), entry.getKind(), null, entry.getRevision(), SVNEventAction.REVERT, null, null, null); } else { event = SVNEventFactory.createSVNEvent(path, SVNNodeKind.UNKNOWN, null, SVNRepository.INVALID_REVISION, SVNEventAction.REVERT, null, null, null); } dispatchEvent(event); } } if (entry != null && entry.getKind() == SVNNodeKind.DIR && depth.compareTo(SVNDepth.EMPTY) > 0) { SVNDepth depthBelowHere = depth; if (depth == SVNDepth.FILES || depth == SVNDepth.IMMEDIATES) { depthBelowHere = SVNDepth.EMPTY; } for (Iterator entries = dir.entries(false); entries.hasNext();) { SVNEntry childEntry = (SVNEntry) entries.next(); if (dir.getThisDirName().equals(childEntry.getName())) { continue; } if (depth == SVNDepth.FILES && !childEntry.isFile()) { continue; } File childPath = new File(path, childEntry.getName()); reverted |= doRevert(childPath, dir, depthBelowHere, useCommitTimes, changeLists); } Map conflicts = SVNTreeConflictUtil.readTreeConflicts(path, entry.getTreeConflictData()); for (Iterator conflictsIter = conflicts.keySet().iterator(); conflictsIter.hasNext();) { File conflictedPath = (File) conflictsIter.next(); if (dir.getEntry(conflictedPath.getName(), false) == null) { reverted |= doRevert(conflictedPath, dir, SVNDepth.EMPTY, useCommitTimes, changeLists); } } } return reverted; } private boolean revert(SVNAdminArea dir, String name, SVNEntry entry, boolean useCommitTime) throws SVNException { SVNLog log = dir.getLog(); boolean reverted = false; SVNVersionedProperties baseProperties = null; SVNProperties command = new SVNProperties(); boolean revertBase = false; if (entry.isScheduledForReplacement()) { revertBase = true; baseProperties = dir.getRevertProperties(name); String propRevertPath = SVNAdminUtil.getPropRevertPath(name, entry.getKind(), false); command.put(SVNLog.NAME_ATTR, propRevertPath); log.addCommand(SVNLog.DELETE, command, false); command.clear(); reverted = true; } boolean reinstallWorkingFile = false; if (baseProperties == null) { if (dir.hasPropModifications(name)) { baseProperties = dir.getBaseProperties(name); SVNVersionedProperties propDiff = dir.getProperties(name).compareTo(baseProperties); Collection propNames = propDiff.getPropertyNames(null); reinstallWorkingFile = propNames.contains(SVNProperty.EXECUTABLE) || propNames.contains(SVNProperty.KEYWORDS) || propNames.contains(SVNProperty.EOL_STYLE) || propNames.contains(SVNProperty.CHARSET) || propNames.contains(SVNProperty.SPECIAL) || propNames.contains(SVNProperty.NEEDS_LOCK); } } if (baseProperties != null) { SVNProperties newProperties = baseProperties.asMap(); SVNVersionedProperties originalBaseProperties = dir.getBaseProperties(name); SVNVersionedProperties workProperties = dir.getProperties(name); if (revertBase) { originalBaseProperties.removeAll(); } workProperties.removeAll(); for (Iterator names = newProperties.nameSet().iterator(); names.hasNext();) { String propName = (String) names.next(); if (revertBase) { originalBaseProperties.setPropertyValue(propName, newProperties.getSVNPropertyValue(propName)); } workProperties.setPropertyValue(propName, newProperties.getSVNPropertyValue(propName)); } dir.saveVersionedProperties(log, false); reverted = true; } SVNProperties newEntryProperties = new SVNProperties(); if (entry.getKind() == SVNNodeKind.FILE) { String basePath = SVNAdminUtil.getTextBasePath(name, false); String revertBasePath = SVNAdminUtil.getTextRevertPath(name, false); if (!reinstallWorkingFile) { SVNFileType fileType = SVNFileType.getType(dir.getFile(name)); if (fileType == SVNFileType.NONE) { reinstallWorkingFile = true; } } if (dir.getFile(revertBasePath).isFile()) { reinstallWorkingFile = true; } else { if (!dir.getFile(basePath).isFile()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Error restoring text for ''{0}''", dir.getFile(name)); SVNErrorManager.error(err, SVNLogType.WC); } revertBasePath = null; } if (revertBasePath != null) { command.put(SVNLog.NAME_ATTR, revertBasePath); command.put(SVNLog.DEST_ATTR, name); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, revertBasePath); command.put(SVNLog.DEST_ATTR, basePath); log.addCommand(SVNLog.MOVE, command, false); reverted = true; } else { if (!reinstallWorkingFile) { reinstallWorkingFile = dir.hasTextModifications(name, false, false, false); } if (reinstallWorkingFile) { command.put(SVNLog.NAME_ATTR, SVNAdminUtil.getTextBasePath(name, false)); command.put(SVNLog.DEST_ATTR, name); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); if (useCommitTime && entry.getCommittedDate() != null) { command.put(SVNLog.NAME_ATTR, name); command.put(SVNLog.TIMESTAMP_ATTR, entry.getCommittedDate()); log.addCommand(SVNLog.SET_TIMESTAMP, command, false); command.clear(); } else { command.put(SVNLog.NAME_ATTR, name); command.put(SVNLog.TIMESTAMP_ATTR, SVNDate.formatDate(new Date(System.currentTimeMillis()))); log.addCommand(SVNLog.SET_TIMESTAMP, command, false); command.clear(); } command.put(SVNLog.NAME_ATTR, name); command.put(SVNProperty.shortPropertyName(SVNProperty.TEXT_TIME), SVNLog.WC_TIMESTAMP); log.addCommand(SVNLog.MODIFY_ENTRY, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, name); command.put(SVNProperty.shortPropertyName(SVNProperty.WORKING_SIZE), SVNLog.WC_WORKING_SIZE); log.addCommand(SVNLog.MODIFY_ENTRY, command, false); command.clear(); } reverted |= reinstallWorkingFile; } } if (entry.getConflictNew() != null) { command.put(SVNLog.NAME_ATTR, entry.getConflictNew()); log.addCommand(SVNLog.DELETE, command, false); command.clear(); newEntryProperties.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_NEW), (String) null); if (!reverted) { reverted |= dir.getFile(entry.getConflictNew()).exists(); } } if (entry.getConflictOld() != null) { command.put(SVNLog.NAME_ATTR, entry.getConflictOld()); log.addCommand(SVNLog.DELETE, command, false); command.clear(); newEntryProperties.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_OLD), (String) null); if (!reverted) { reverted |= dir.getFile(entry.getConflictOld()).exists(); } } if (entry.getConflictWorking() != null) { command.put(SVNLog.NAME_ATTR, entry.getConflictWorking()); log.addCommand(SVNLog.DELETE, command, false); command.clear(); newEntryProperties.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_WRK), (String) null); if (!reverted) { reverted |= dir.getFile(entry.getConflictWorking()).exists(); } } if (entry.getPropRejectFile() != null) { command.put(SVNLog.NAME_ATTR, entry.getPropRejectFile()); log.addCommand(SVNLog.DELETE, command, false); command.clear(); newEntryProperties.put(SVNProperty.shortPropertyName(SVNProperty.PROP_REJECT_FILE), (String) null); if (!reverted) { reverted |= dir.getFile(entry.getPropRejectFile()).exists(); } } if (entry.isScheduledForReplacement()) { newEntryProperties.put(SVNProperty.shortPropertyName(SVNProperty.COPIED), SVNProperty.toString(false)); newEntryProperties.put(SVNProperty.shortPropertyName(SVNProperty.COPYFROM_URL), (String) null); newEntryProperties.put(SVNProperty.shortPropertyName(SVNProperty.COPYFROM_REVISION), SVNProperty.toString(SVNRepository.INVALID_REVISION)); if (entry.isFile() && entry.getCopyFromURL() != null) { String basePath = SVNAdminUtil.getTextRevertPath(name, false); File baseFile = dir.getFile(basePath); String digest = SVNFileUtil.computeChecksum(baseFile); newEntryProperties.put(SVNProperty.shortPropertyName(SVNProperty.CHECKSUM), digest); } } if (entry.getSchedule() != null) { newEntryProperties.put(SVNProperty.shortPropertyName(SVNProperty.SCHEDULE), (String) null); reverted = true; } if (!newEntryProperties.isEmpty()) { newEntryProperties.put(SVNLog.NAME_ATTR, name); log.addCommand(SVNLog.MODIFY_ENTRY, newEntryProperties, false); } log.save(); dir.runLogs(); return reverted; } private void setWCFormat(SVNAdminAreaInfo info, SVNAdminArea area, int format) throws SVNException { if (!isIgnoreExternals()) { SVNVersionedProperties props = area.getProperties(area.getThisDirName()); SVNVersionedProperties baseProps = area.getBaseProperties(area.getThisDirName()); SVNPropertyValue property = props.getPropertyValue(SVNProperty.EXTERNALS); SVNPropertyValue baseProperty = baseProps.getPropertyValue(SVNProperty.EXTERNALS); if (property != null || baseProperty != null) { String areaPath = area.getRelativePath(info.getAnchor()); info.addExternal(areaPath, property != null ? property.getString() : null, baseProperty != null ? baseProperty.getString() : null); } } area.getWCAccess().closeAdminArea(area.getRoot()); area = area.getWCAccess().open(area.getRoot(), true, false, false, 0, Level.FINE); SVNAdminArea newArea = SVNAdminAreaFactory.changeWCFormat(area, format); for (Iterator entries = newArea.entries(false); entries.hasNext();) { SVNEntry entry = (SVNEntry) entries.next(); if (entry.isThisDir() || entry.isFile()) { continue; } File childDir = new File(newArea.getRoot(), entry.getName()); SVNAdminArea childArea = newArea.getWCAccess().getAdminArea(childDir); if (childArea != null) { setWCFormat(info, childArea, format); } } } private void pushDirInfo(SVNRepository repos, SVNRevision rev, String path, SVNURL root, String uuid, SVNURL url, Map locks, SVNDepth depth, ISVNInfoHandler handler) throws SVNException { Collection children = repos.getDir(path, rev.getNumber(), null, SVNDirEntry.DIRENT_KIND | SVNDirEntry.DIRENT_CREATED_REVISION | SVNDirEntry.DIRENT_TIME | SVNDirEntry.DIRENT_LAST_AUTHOR, new ArrayList()); for (Iterator ents = children.iterator(); ents.hasNext();) { checkCancelled(); SVNDirEntry child = (SVNDirEntry) ents.next(); SVNURL childURL = url.appendPath(child.getName(), false); String childPath = SVNPathUtil.append(path, child.getName()); String displayPath = repos.getFullPath(childPath); displayPath = displayPath.substring(repos.getLocation().getPath().length()); if (displayPath.startsWith("/")) { displayPath = displayPath.substring(1); } if ("".equals(displayPath)) { displayPath = path; } SVNLock lock = (SVNLock) locks.get(path); SVNInfo info = SVNInfo.createInfo(displayPath, root, uuid, childURL, rev, child, lock); if (depth.compareTo(SVNDepth.IMMEDIATES) >= 0 || (depth == SVNDepth.FILES && child.getKind() == SVNNodeKind.FILE)) { handler.handleInfo(info); } if (depth == SVNDepth.INFINITY && child.getKind() == SVNNodeKind.DIR) { pushDirInfo(repos, rev, SVNPathUtil.append(path, child.getName()), root, uuid, childURL, locks, depth, handler); } } } private void doGetRemoteProperty(SVNURL url, String path, SVNRepository repos, String propName, SVNRevision rev, SVNDepth depth, ISVNPropertyHandler handler) throws SVNException { checkCancelled(); long revNumber = getRevisionNumber(rev, repos, null); SVNNodeKind kind = repos.checkPath(path, revNumber); SVNProperties props = new SVNProperties(); if (kind == SVNNodeKind.DIR) { Collection children = repos.getDir(path, revNumber, props, SVNDirEntry.DIRENT_KIND, SVNDepth.FILES.compareTo(depth) <= 0 ? new ArrayList() : null); if (propName != null) { SVNPropertyValue value = props.getSVNPropertyValue(propName); if (value != null) { handler.handleProperty(url, new SVNPropertyData(propName, value, getOptions())); } } else { for (Iterator names = props.nameSet().iterator(); names.hasNext();) { String name = (String) names.next(); if (name.startsWith(SVNProperty.SVN_ENTRY_PREFIX) || name.startsWith(SVNProperty.SVN_WC_PREFIX)) { continue; } SVNPropertyValue value = props.getSVNPropertyValue(name); handler.handleProperty(url, new SVNPropertyData(name, value, getOptions())); } } if (SVNDepth.FILES.compareTo(depth) <= 0) { checkCancelled(); for (Iterator entries = children.iterator(); entries.hasNext();) { SVNDirEntry child = (SVNDirEntry) entries.next(); SVNURL childURL = url.appendPath(child.getName(), false); String childPath = "".equals(path) ? child.getName() : SVNPathUtil.append(path, child.getName()); SVNDepth depthBelowHere = depth; if (child.getKind() == SVNNodeKind.DIR && depth == SVNDepth.FILES) { continue; } if (depth == SVNDepth.FILES || depth == SVNDepth.IMMEDIATES) { depthBelowHere = SVNDepth.EMPTY; } doGetRemoteProperty(childURL, childPath, repos, propName, rev, depthBelowHere, handler); } } } else if (kind == SVNNodeKind.FILE) { repos.getFile(path, revNumber, props, null); if (propName != null) { SVNPropertyValue value = props.getSVNPropertyValue(propName); if (value != null) { handler.handleProperty(url, new SVNPropertyData(propName, value, getOptions())); } } else { for (Iterator names = props.nameSet().iterator(); names.hasNext();) { String name = (String) names.next(); if (name.startsWith(SVNProperty.SVN_ENTRY_PREFIX) || name.startsWith(SVNProperty.SVN_WC_PREFIX)) { continue; } SVNPropertyValue value = props.getSVNPropertyValue(name); handler.handleProperty(url, new SVNPropertyData(name, value, getOptions())); } } } else if (kind == SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' does not exist in revision {1}", new Object[] { path, String.valueOf(revNumber) }); SVNErrorManager.error(err, SVNLogType.WC); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "Unknown node kind for ''{0}''", path); SVNErrorManager.error(err, SVNLogType.WC); } } private void doGetLocalProperty(SVNEntry entry, SVNAdminArea area, String propName, boolean base, ISVNPropertyHandler handler, SVNDepth depth, Collection changeLists) throws SVNException { if (depth == null || depth == SVNDepth.UNKNOWN) { depth = SVNDepth.EMPTY; } File target = area.getFile(entry.getName()); SVNWCAccess wcAccess = area.getWCAccess(); ISVNEntryHandler propGetHandler = new PropFetchHandler(changeLists, propName, handler, base); if (SVNDepth.FILES.compareTo(depth) <= 0 && entry.isDirectory()) { wcAccess.walkEntries(target, propGetHandler, false, depth); } else if (SVNWCAccess.matchesChangeList(changeLists, entry)) { if (propName == null) { SVNVersionedProperties properties = base ? area.getBaseProperties(entry.getName()) : area.getProperties(entry.getName()); SVNProperties allProps = properties.asMap(); for (Iterator names = allProps.nameSet().iterator(); names.hasNext();) { String name = (String) names.next(); SVNPropertyValue val = allProps.getSVNPropertyValue(name); handler.handleProperty(area.getFile(entry.getName()), new SVNPropertyData(name, val, getOptions())); } } else { propGetHandler.handleEntry(target, entry); } } } private void setLocalProperties(File path, SVNEntry entry, SVNAdminArea adminArea, boolean force, ISVNPropertyValueProvider propertyValueProvider, ISVNPropertyHandler handler) throws SVNException { SVNVersionedProperties entryProperties = adminArea.getProperties(entry.getName()); SVNProperties properties = entryProperties.asMap(); SVNProperties unmodifiableProperties = SVNProperties.unmodifiableProperties(properties); SVNProperties changedProperties = propertyValueProvider.providePropertyValues(path, unmodifiableProperties); SVNProperties propDiff = properties.compareTo(changedProperties); for (Iterator iterator = propDiff.nameSet().iterator(); iterator.hasNext();) { String propName = (String) iterator.next(); SVNPropertyValue propValue = propDiff.getSVNPropertyValue(propName); if (propValue != null && !SVNPropertiesManager.isValidPropertyName(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Bad property name ''{0}''", propName); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNRevisionProperty.isRevisionProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Revision property ''{0}'' not allowed in this context", propName); SVNErrorManager.error(err, SVNLogType.WC); } else if (SVNProperty.isWorkingCopyProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "''{0}'' is a wcprop, thus not accessible to clients", propName); SVNErrorManager.error(err, SVNLogType.WC); } else if (SVNProperty.isEntryProperty(propName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Property ''{0}'' is an entry property", propName); SVNErrorManager.error(err, SVNLogType.WC); } try { boolean modified = SVNPropertiesManager.setProperty(adminArea.getWCAccess(), path, propName, propValue, force); if (modified && handler != null) { handler.handleProperty(path, new SVNPropertyData(propName, propValue, getOptions())); } } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.ILLEGAL_TARGET) { throw svne; } } } } private Map fetchLockTokens(SVNRepository repository, Map pathsTokensMap) throws SVNException { Map tokens = new SVNHashMap(); for (Iterator paths = pathsTokensMap.keySet().iterator(); paths.hasNext();) { String path = (String) paths.next(); SVNLock lock = repository.getLock(path); if (lock == null || lock.getID() == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_MISSING_LOCK_TOKEN, "''{0}'' is not locked", path); SVNErrorManager.error(err, SVNLogType.WC); continue; } tokens.put(path, lock.getID()); } return tokens; } private void doGetLocalFileContents(File path, OutputStream dst, SVNRevision revision, boolean expandKeywords) throws SVNException { SVNWCAccess wcAccess = createWCAccess(); InputStream input = null; boolean hasMods = false; SVNVersionedProperties properties = null; try { SVNAdminArea area = wcAccess.open(path.getParentFile(), false, 0); SVNEntry entry = wcAccess.getVersionedEntry(path, false); if (entry.getKind() != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNVERSIONED_RESOURCE, "''{0}'' refers to a directory", path); SVNErrorManager.error(err, SVNLogType.WC); } String name = path.getName(); if (revision != SVNRevision.WORKING) { input = area.getBaseFileForReading(name, false); properties = area.getBaseProperties(name); } else { input = SVNFileUtil.openFileForReading(area.getFile(path.getName()), SVNLogType.WC); hasMods = area.hasPropModifications(name) || area.hasTextModifications(name, true); properties = area.getProperties(name); } String charsetProp = properties.getStringPropertyValue(SVNProperty.CHARSET); String mimeType = properties.getStringPropertyValue(SVNProperty.MIME_TYPE); String eolStyle = properties.getStringPropertyValue(SVNProperty.EOL_STYLE); String keywords = properties.getStringPropertyValue(SVNProperty.KEYWORDS); boolean special = properties.getPropertyValue(SVNProperty.SPECIAL) != null; byte[] eols = null; Map keywordsMap = null; String time = null; String charset = SVNTranslator.getCharset(charsetProp, mimeType, path.getPath(), getOptions()); eols = SVNTranslator.getEOL(eolStyle, getOptions()); if (hasMods && !special) { time = SVNDate.formatDate(new Date(SVNFileUtil.getFileLastModified(path))); } else { time = entry.getCommittedDate(); } if (keywords != null) { String url = entry.getURL(); String repositoryRoot = entry.getRepositoryRoot(); String author = hasMods ? "(local)" : entry.getAuthor(); String rev = hasMods ? entry.getCommittedRevision() + "M" : entry.getCommittedRevision() + ""; keywordsMap = SVNTranslator.computeKeywords(keywords, expandKeywords ? url : null, repositoryRoot, author, time, rev, getOptions()); } OutputStream translatingStream = charset != null || eols != null || keywordsMap != null ? SVNTranslator.getTranslatingOutputStream(dst, charset, eols, false, keywordsMap, expandKeywords) : dst; try { SVNTranslator.copy(input, new SVNCancellableOutputStream(translatingStream, getEventDispatcher())); dst.flush(); } catch (IOExceptionWrapper ioew) { throw ioew.getOriginalException(); } catch (IOException e) { if (e instanceof SVNCancellableOutputStream.IOCancelException) { SVNErrorManager.cancel(e.getMessage(), SVNLogType.NETWORK); } SVNTranslator.translationError(path, e); } } finally { SVNFileUtil.closeFile(input); wcAccess.close(); } } private static class LockInfo { public LockInfo(File file, SVNRevision rev) { myFile = file; myRevision = rev; } public LockInfo(File file, String token) { myFile = file; myToken = token; } private File myFile; private SVNRevision myRevision; private String myToken; } private class PropSetHandler implements ISVNEntryHandler { private boolean myIsForce; private String myPropName; private SVNPropertyValue myPropValue; private ISVNPropertyHandler myPropHandler; private Collection myChangeLists; public PropSetHandler(boolean isForce, String propName, SVNPropertyValue propValue, ISVNPropertyHandler handler, Collection changeLists) { myIsForce = isForce; myPropName = propName; myPropValue = propValue; myPropHandler = handler; myChangeLists = changeLists; } public void handleEntry(File path, SVNEntry entry) throws SVNException { SVNAdminArea adminArea = entry.getAdminArea(); if (entry.isDirectory() && !adminArea.getThisDirName().equals(entry.getName())) { return; } if (entry.isScheduledForDeletion()) { return; } if (!SVNWCAccess.matchesChangeList(myChangeLists, entry)) { return; } try { boolean modified = SVNPropertiesManager.setProperty(adminArea.getWCAccess(), path, myPropName, myPropValue, myIsForce); if (modified && myPropHandler != null) { myPropHandler.handleProperty(path, new SVNPropertyData(myPropName, myPropValue, getOptions())); } } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.ILLEGAL_TARGET) { throw svne; } } } public void handleError(File path, SVNErrorMessage error) throws SVNException { SVNErrorManager.error(error, SVNLogType.WC); } } private class PropSetHandlerExt implements ISVNEntryHandler { private boolean myIsForce; private ISVNPropertyValueProvider myPropValueProvider; private ISVNPropertyHandler myPropHandler; private Collection myChangeLists; public PropSetHandlerExt(boolean isForce, ISVNPropertyValueProvider propertyValueProvider, ISVNPropertyHandler handler, Collection changeLists) { myIsForce = isForce; myPropValueProvider = propertyValueProvider; myPropHandler = handler; myChangeLists = changeLists; } public void handleEntry(File path, SVNEntry entry) throws SVNException { SVNAdminArea adminArea = entry.getAdminArea(); if (entry.isDirectory() && !adminArea.getThisDirName().equals(entry.getName())) { return; } if (entry.isScheduledForDeletion()) { return; } if (!SVNWCAccess.matchesChangeList(myChangeLists, entry)) { return; } setLocalProperties(path, entry, adminArea, myIsForce, myPropValueProvider, myPropHandler); } public void handleError(File path, SVNErrorMessage error) throws SVNException { SVNErrorManager.error(error, SVNLogType.WC); } } private class PropFetchHandler implements ISVNEntryHandler { private Collection myChangeLists; private boolean myIsPristine; private String myPropName; private ISVNPropertyHandler myPropHandler; public PropFetchHandler(Collection changeLists, String propName, ISVNPropertyHandler handler, boolean pristine) { myChangeLists = changeLists; myIsPristine = pristine; myPropName = propName; myPropHandler = handler; } public void handleEntry(File path, SVNEntry entry) throws SVNException { SVNAdminArea adminArea = entry.getAdminArea(); if (entry.isDirectory() && !entry.isThisDir()) { return; } if ((myIsPristine && entry.isScheduledForAddition()) || (!myIsPristine && entry.isScheduledForDeletion())) { return; } if (!SVNWCAccess.matchesChangeList(myChangeLists, entry)) { return; } SVNVersionedProperties properties = myIsPristine ? adminArea.getBaseProperties(entry.getName()) : adminArea.getProperties(entry.getName()); if (myPropName != null) { SVNPropertyValue propValue = properties.getPropertyValue(myPropName); if (propValue != null) { myPropHandler.handleProperty(path, new SVNPropertyData(myPropName, propValue, getOptions())); } } else { SVNProperties allProps = properties.asMap(); for (Iterator names = allProps.nameSet().iterator(); names.hasNext();) { String name = (String) names.next(); SVNPropertyValue val = allProps.getSVNPropertyValue(name); myPropHandler.handleProperty(path, new SVNPropertyData(name, val, getOptions())); } } } public void handleError(File path, SVNErrorMessage error) throws SVNException { SVNErrorManager.error(error, SVNLogType.WC); } } }