package org.tmatesoft.svn.core.internal.wc16; import java.io.File; import java.util.Iterator; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNCopyDriver; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNEventFactory; import org.tmatesoft.svn.core.internal.wc.SVNFileType; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea; 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.SVNVersionedProperties; import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.ISVNEventHandler; import org.tmatesoft.svn.core.wc.ISVNOptions; import org.tmatesoft.svn.core.wc.ISVNRepositoryPool; import org.tmatesoft.svn.core.wc.SVNCopySource; import org.tmatesoft.svn.core.wc.SVNEvent; import org.tmatesoft.svn.core.wc.SVNEventAction; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNWCUtil; import org.tmatesoft.svn.util.ISVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; /** * The <b>SVNMoveClient</b> provides an extra client-side functionality over * standard (i.e. compatible with the SVN command line client) move operations. * This class helps to overcome the SVN limitations regarding move operations. * Using <b>SVNMoveClient</b> you can easily: * <ul> * <li>move versioned items to other versioned ones within the same Working * Copy, what even allows to replace items scheduled for deletion, or those that * are missing but are still under version control and have a node kind * different from the node kind of the source (!); * <li>move versioned items belonging to one Working Copy to versioned items * that belong to absolutely different Working Copy; * <li>move versioned items to unversioned ones; * <li>move unversioned items to versioned ones; * <li>move unversioned items to unversioned ones; * <li>revert any of the kinds of moving listed above; * <li>complete a copy/move operation for a file, that is if you have manually * copied/moved a versioned file to an unversioned file in a Working copy, you * can run a 'virtual' copy/move on these files to copy/move all the necessary * administrative version control information. * </ul> * * @version 1.3 * @author TMate Software Ltd. * @since 1.2 */ public class SVNMoveClient16 extends SVNCopyDriver { private SVNWCClient16 myWCClient; private SVNCopyClient16 myCopyClient; /** * Constructs and initializes an <b>SVNMoveClient</b> object with the * specified run-time configuration and authentication drivers. * <p> * If <code>options</code> is <span class="javakeyword">null</span>, then * this <b>SVNMoveClient</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> * If <code>authManager</code> is <span class="javakeyword">null</span>, * then this <b>SVNMoveClient</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 SVNMoveClient16(ISVNAuthenticationManager authManager, ISVNOptions options) { super(authManager, options); myWCClient = new SVNWCClient16(authManager, options); myCopyClient = new SVNCopyClient16(authManager, options); } /** * Constructs and initializes an <b>SVNMoveClient</b> object with the * specified run-time configuration and repository pool object. * <p/> * If <code>options</code> is <span class="javakeyword">null</span>, then * this <b>SVNMoveClient</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/> * 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 SVNMoveClient16(ISVNRepositoryPool repositoryPool, ISVNOptions options) { super(repositoryPool, options); myWCClient = new SVNWCClient16(repositoryPool, options); myCopyClient = new SVNCopyClient16(repositoryPool, options); } public void setEventHandler(ISVNEventHandler dispatcher) { super.setEventHandler(dispatcher); myWCClient.setEventHandler(dispatcher); myCopyClient.setEventHandler(dispatcher); } public void setDebugLog(ISVNDebugLog log) { super.setDebugLog(log); myWCClient.setDebugLog(log); myCopyClient.setDebugLog(log); } public void setOptions(ISVNOptions options) { super.setOptions(options); if (myWCClient != null) { myWCClient.setOptions(options); } if (myCopyClient != null) { myCopyClient.setOptions(options); } } /** * Moves a source item to a destination one. * <p> * <code>dst</code> should not exist. Furher it's considered to be versioned * if its parent directory is under version control, otherwise * <code>dst</code> is considered to be unversioned. * <p> * If both <code>src</code> and <code>dst</code> are unversioned, then * simply moves <code>src</code> to <code>dst</code> in the filesystem. * <p> * If <code>src</code> is versioned but <code>dst</code> is not, then * exports <code>src</code> to <code>dst</code> in the filesystem and * removes <code>src</code> from version control. * <p> * If <code>dst</code> is versioned but <code>src</code> is not, then moves * <code>src</code> to <code>dst</code> (even if <code>dst</code> is * scheduled for deletion). * <p> * If both <code>src</code> and <code>dst</code> are versioned and located * within the same Working Copy, then moves <code>src</code> to * <code>dst</code> (even if <code>dst</code> is scheduled for deletion), or * tries to replace <code>dst</code> with <code>src</code> if the former is * missing and has a node kind different from the node kind of the source. * If <code>src</code> is scheduled for addition with history, * <code>dst</code> will be set the same ancestor URL and revision from * which the source was copied. If <code>src</code> and <code>dst</code> are * located in different Working Copies, then this method copies * <code>src</code> to <code>dst</code>, tries to put the latter under * version control and finally removes <code>src</code>. * * @param src * a source path * @param dst * a destination path * @throws SVNException * if one of the following is true: * <ul> * <li><code>dst</code> already exists <li><code>src</code> does * not exist * </ul> */ public void doMove(File src, File dst) throws SVNException { if (dst.exists()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "File ''{0}'' already exists", dst); SVNErrorManager.error(err, SVNLogType.WC); } else if (!src.exists()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "Path ''{0}'' does not exist", src); SVNErrorManager.error(err, SVNLogType.WC); } boolean srcIsVersioned = isVersionedFile(src); boolean dstParentIsVersioned = isVersionedFile(dst.getParentFile()); if (!srcIsVersioned && !dstParentIsVersioned) { SVNFileUtil.rename(src, dst); } else if (!dstParentIsVersioned) { SVNFileUtil.copy(src, dst, false, false); myWCClient.doDelete(src, true, false); } else if (!srcIsVersioned) { SVNFileUtil.rename(src, dst); } else { SVNWCAccess wcAccess = createWCAccess(); File srcParent = src.getParentFile(); File dstParent = dst.getParentFile(); SVNAdminArea srcParentArea = null; SVNAdminArea dstParentArea = null; try { if (srcParent.equals(dstParent)) { wcAccess.closeAdminArea(srcParent); srcParentArea = dstParentArea = wcAccess.open(srcParent, true, 0); } else { srcParentArea = wcAccess.open(srcParent, false, 0); dstParentArea = wcAccess.open(dstParent, true, 0); } SVNEntry srcEntry = srcParentArea.getVersionedEntry(src.getName(), false); SVNEntry dstEntry = dstParentArea.getEntry(dst.getName(), false); File srcWCRoot = SVNWCUtil.getWorkingCopyRoot(src, true); File dstWCRoot = SVNWCUtil.getWorkingCopyRoot(dst, true); boolean sameWC = srcWCRoot != null && srcWCRoot.equals(dstWCRoot); if (sameWC && dstEntry != null && (dstEntry.isScheduledForDeletion() || dstEntry.getKind() != srcEntry.getKind())) { wcAccess.close(); if (srcEntry.getKind() == dstEntry.getKind() && srcEntry.getSchedule() == null && srcEntry.isFile()) { SVNCopySource source = new SVNCopySource(SVNRevision.UNDEFINED, SVNRevision.WORKING, src); myCopyClient.doCopy(new SVNCopySource[] { source }, dst, true, false, true); return; } SVNFileUtil.copy(src, dst, false, false); try { myWCClient.doAdd(dst, false, false, false, SVNDepth.INFINITY, false, false); } catch (SVNException e) { } myWCClient.doDelete(src, true, false); return; } else if (!sameWC) { SVNEntry dstTmpEntry = dstEntry != null && dstEntry.isThisDir() ? dstEntry : dstParentArea.getVersionedEntry(dstParentArea.getThisDirName(), false); SVNEntry srcTmpEntry = srcEntry != null && srcEntry.isThisDir() ? srcEntry : srcParentArea.getVersionedEntry(srcParentArea.getThisDirName(), false); if (srcTmpEntry.getRepositoryRoot() != null && dstTmpEntry.getRepositoryRoot() != null && srcTmpEntry.getRepositoryRoot().equals(dstTmpEntry.getRepositoryRoot())) { wcAccess.close(); SVNCopySource source = new SVNCopySource(SVNRevision.UNDEFINED, SVNRevision.WORKING, src); myCopyClient.doCopy(new SVNCopySource[] { source }, dst, true, false, true); return; } } if (dstEntry != null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "There is already a versioned item ''{0}''", dst); SVNErrorManager.error(err, SVNLogType.WC); } SVNFileUtil.copy(src, dst, false, sameWC); if (!sameWC) { wcAccess.close(); try { myWCClient.doAdd(dst, false, false, false, SVNDepth.INFINITY, false, false); } catch (SVNException e) { } } else if (srcEntry.isFile()) { if (dstEntry == null) { dstEntry = dstParentArea.addEntry(dst.getName()); } String srcURL = srcEntry.getURL(); String srcCFURL = srcEntry.getCopyFromURL(); long srcRevision = srcEntry.getRevision(); long srcCFRevision = srcEntry.getCopyFromRevision(); SVNVersionedProperties srcProps = srcParentArea.getProperties(src.getName()); SVNVersionedProperties dstProps = dstParentArea.getProperties(dst.getName()); srcProps.copyTo(dstProps); File srcBaseFile = srcParentArea.getBaseFile(src.getName(), false); File dstBaseFile = dstParentArea.getBaseFile(dst.getName(), false); if (srcBaseFile.isFile()) { SVNFileUtil.copy(srcBaseFile, dstBaseFile, false, false); } if (srcEntry.isScheduledForAddition() && srcEntry.isCopied()) { dstEntry.scheduleForAddition(); dstEntry.setCopyFromRevision(srcCFRevision); dstEntry.setCopyFromURL(srcCFURL); dstEntry.setKind(SVNNodeKind.FILE); dstEntry.setRevision(srcRevision); dstEntry.setCopied(true); } else if (!srcEntry.isCopied() && !srcEntry.isScheduledForAddition()) { dstEntry.setCopied(true); dstEntry.scheduleForAddition(); dstEntry.setKind(SVNNodeKind.FILE); dstEntry.setCopyFromRevision(srcRevision); dstEntry.setCopyFromURL(srcURL); } else { dstEntry.scheduleForAddition(); dstEntry.setKind(SVNNodeKind.FILE); if (!dstEntry.isScheduledForReplacement()) { dstEntry.setRevision(0); } } SVNLog log = dstParentArea.getLog(); dstParentArea.saveEntries(false); dstParentArea.saveVersionedProperties(log, true); log.save(); dstParentArea.runLogs(); } else if (srcEntry.isDirectory()) { SVNAdminArea srcArea = wcAccess.open(src, false, 0); srcEntry = srcArea.getEntry(srcArea.getThisDirName(), false); if (dstEntry == null) { dstEntry = dstParentArea.addEntry(dst.getName()); } SVNAdminArea dstArea = wcAccess.open(dst, true, SVNWCAccess.INFINITE_DEPTH); SVNVersionedProperties srcProps = srcArea.getProperties(srcArea.getThisDirName()); SVNVersionedProperties dstProps = dstArea.getProperties(dstArea.getThisDirName()); SVNEntry dstParentEntry = dstParentArea.getEntry(dstParentArea.getThisDirName(), false); String srcURL = srcEntry.getURL(); String srcCFURL = srcEntry.getCopyFromURL(); String dstURL = dstParentEntry.getURL(); String repositoryRootURL = dstParentEntry.getRepositoryRoot(); long srcRevision = srcEntry.getRevision(); long srcCFRevision = srcEntry.getCopyFromRevision(); dstURL = SVNPathUtil.append(dstURL, SVNEncodingUtil.uriEncode(dst.getName())); if (srcEntry.isScheduledForAddition() && srcEntry.isCopied()) { srcProps.copyTo(dstProps); dstEntry.scheduleForAddition(); dstEntry.setKind(SVNNodeKind.DIR); dstEntry.setCopied(true); dstEntry.setCopyFromRevision(srcCFRevision); dstEntry.setCopyFromURL(srcCFURL); SVNEntry dstThisEntry = dstArea.getEntry(dstArea.getThisDirName(), false); dstThisEntry.scheduleForAddition(); dstThisEntry.setKind(SVNNodeKind.DIR); dstThisEntry.setCopyFromRevision(srcCFRevision); dstThisEntry.setCopyFromURL(srcCFURL); dstThisEntry.setRevision(srcRevision); dstThisEntry.setCopied(true); SVNLog log = dstArea.getLog(); dstArea.saveVersionedProperties(log, true); dstParentArea.saveEntries(false); log.save(); dstArea.runLogs(); dstArea.updateURL(dstURL, true); dstParentArea.saveEntries(true); } else if (!srcEntry.isCopied() && !srcEntry.isScheduledForAddition()) { srcProps.copyTo(dstProps); dstEntry.scheduleForAddition(); dstEntry.setKind(SVNNodeKind.DIR); dstEntry.setCopied(true); dstEntry.setCopyFromRevision(srcRevision); dstEntry.setCopyFromURL(srcURL); SVNEntry dstThisEntry = dstArea.getEntry(dstArea.getThisDirName(), false); dstThisEntry.scheduleForAddition(); dstThisEntry.setKind(SVNNodeKind.DIR); dstThisEntry.setCopied(true); dstThisEntry.scheduleForAddition(); dstThisEntry.setKind(SVNNodeKind.DIR); dstThisEntry.setCopyFromRevision(srcRevision); dstThisEntry.setCopyFromURL(srcURL); dstThisEntry.setURL(dstURL); dstThisEntry.setRepositoryRoot(repositoryRootURL); SVNLog log = dstArea.getLog(); dstArea.saveVersionedProperties(log, true); dstArea.saveEntries(false); log.save(); dstArea.runLogs(); updateCopiedDirectory(dstArea, dstArea.getThisDirName(), dstURL, repositoryRootURL, null, -1); dstArea.saveEntries(true); dstParentArea.saveEntries(true); } else { dstParentArea.deleteEntry(dst.getName()); dstParentArea.saveEntries(true); SVNFileUtil.deleteAll(dst, this); SVNFileUtil.copy(src, dst, false, false); wcAccess.close(); myWCClient.doAdd(dst, false, false, false, SVNDepth.INFINITY, false, false); } } try { wcAccess.close(); myWCClient.doDelete(src, true, false); } catch (SVNException e) { } } finally { wcAccess.close(); } } } /** * Reverts a previous move operation back. Provided in pair with * {@link #doMove(File,File) doMove()} and used to roll back move * operations. In this case <code>src</code> is considered to be the target * of the previsous move operation, and <code>dst</code> is regarded to be * the source of that same operation which have been moved to * <code>src</code> and now is to be restored. * <p> * <code>dst</code> could exist in that case if it has been a WC directory * that was scheduled for deletion during the previous move operation. * Furher <code>dst</code> is considered to be versioned if its parent * directory is under version control, otherwise <code>dst</code> is * considered to be unversioned. * <p> * If both <code>src</code> and <code>dst</code> are unversioned, then * simply moves <code>src</code> back to <code>dst</code> in the filesystem. * <p> * If <code>src</code> is versioned but <code>dst</code> is not, then * unmoves <code>src</code> to <code>dst</code> in the filesystem and * removes <code>src</code> from version control. * <p> * If <code>dst</code> is versioned but <code>src</code> is not, then first * tries to make a revert on <code>dst</code> - if it has not been committed * yet, it will be simply reverted. However in the case <code>dst</code> has * been already removed from the repository, <code>src</code> will be copied * back to <code>dst</code> and scheduled for addition. Then * <code>src</code> is removed from the filesystem. * <p> * If both <code>src</code> and <code>dst</code> are versioned then the * following situations are possible: * <ul> * <li>If <code>dst</code> is still scheduled for deletion, then it is * reverted back and <code>src</code> is scheduled for deletion. * <li>in the case if <code>dst</code> exists but is not scheduled for * deletion, <code>src</code> is cleanly exported to <code>dst</code> and * removed from version control. * <li>if <code>dst</code> and <code>src</code> are from different * repositories (appear to be in different Working Copies), then * <code>src</code> is copied to <code>dst</code> (with scheduling * <code>dst</code> for addition, but not with history since copying is made * in the filesystem only) and removed from version control. * <li>if both <code>dst</code> and <code>src</code> are in the same * repository (appear to be located in the same Working Copy) and: * <ul style="list-style-type: lower-alpha"> * <li>if <code>src</code> is scheduled for addition with history, then * copies <code>src</code> to <code>dst</code> specifying the source * ancestor's URL and revision (i.e. the ancestor of the source is the * ancestor of the destination); * <li>if <code>src</code> is already under version control, then copies * <code>src</code> to <code>dst</code> specifying the source URL and * revision as the ancestor (i.e. <code>src</code> itself is the ancestor of * <code>dst</code>); * <li>if <code>src</code> is just scheduled for addition (without history), * then simply copies <code>src</code> to <code>dst</code> (only in the * filesystem, without history) and schedules <code>dst</code> for addition; * </ul> * then <code>src</code> is removed from version control. * </ul> * * @param src * a source path * @param dst * a destination path * @throws SVNException * if <code>src</code> does not exist */ public void undoMove(File src, File dst) throws SVNException { if (!src.exists()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "Path ''{0}'' does not exist", src); SVNErrorManager.error(err, SVNLogType.WC); } boolean srcIsVersioned = isVersionedFile(src); boolean dstParentIsVersioned = isVersionedFile(dst.getParentFile()); if (!srcIsVersioned && !dstParentIsVersioned) { SVNFileUtil.rename(src, dst); } else if (!dstParentIsVersioned) { SVNFileUtil.copy(src, dst, false, false); myWCClient.doDelete(src, true, false); } else if (!srcIsVersioned) { SVNFileUtil.rename(src, dst); SVNWCAccess dstAccess = createWCAccess(); boolean revert = false; try { dstAccess.probeOpen(dst, false, 0); SVNEntry dstEntry = dstAccess.getEntry(dst, false); revert = dstEntry != null && dstEntry.isScheduledForDeletion(); } catch (SVNException e) { } finally { dstAccess.close(); } if (revert) { myWCClient.doRevert(new File[] { dst }, SVNDepth.INFINITY, null); } else { } } else { SVNWCAccess wcAccess = createWCAccess(); File srcParent = src.getParentFile(); File dstParent = dst.getParentFile(); SVNAdminArea srcParentArea = null; SVNAdminArea dstParentArea = null; try { if (srcParent.equals(dstParent)) { wcAccess.closeAdminArea(srcParent); srcParentArea = dstParentArea = wcAccess.open(srcParent, true, 0); } else { srcParentArea = wcAccess.open(srcParent, false, 0); dstParentArea = wcAccess.open(dstParent, true, 0); } SVNEntry srcEntry = srcParentArea.getEntry(src.getName(), true); SVNEntry dstEntry = dstParentArea.getEntry(dst.getName(), true); if (dstEntry != null && dstEntry.isScheduledForDeletion()) { wcAccess.close(); myWCClient.doRevert(new File[] { dst }, SVNDepth.INFINITY, null); myWCClient.doDelete(src, true, false); return; } SVNEntry dstParentEntry = wcAccess.getEntry(dstParent, false); File srcWCRoot = SVNWCUtil.getWorkingCopyRoot(src, true); File dstWCRoot = SVNWCUtil.getWorkingCopyRoot(dst, true); boolean sameWC = srcWCRoot != null && srcWCRoot.equals(dstWCRoot); SVNFileUtil.copy(src, dst, false, sameWC); if (dstEntry != null && dstEntry.getKind() != srcEntry.getKind()) { wcAccess.close(); myWCClient.doDelete(src, true, false); return; } if (!sameWC) { wcAccess.close(); try { myWCClient.doAdd(dst, false, false, false, SVNDepth.INFINITY, false, false); } catch (SVNException e) { } } else if (srcEntry.isFile()) { if (dstEntry == null) { dstEntry = dstParentArea.addEntry(dst.getName()); } String srcURL = srcEntry.getURL(); String srcCFURL = srcEntry.getCopyFromURL(); long srcRevision = srcEntry.getRevision(); long srcCFRevision = srcEntry.getCopyFromRevision(); if (srcEntry.isScheduledForAddition() && srcEntry.isCopied()) { dstEntry.scheduleForAddition(); dstEntry.setCopyFromRevision(srcCFRevision); dstEntry.setCopyFromURL(srcCFURL); dstEntry.setKind(SVNNodeKind.FILE); dstEntry.setRevision(srcRevision); dstEntry.setCopied(true); } else if (!srcEntry.isCopied() && !srcEntry.isScheduledForAddition()) { dstEntry.setCopied(true); dstEntry.scheduleForAddition(); dstEntry.setKind(SVNNodeKind.FILE); dstEntry.setCopyFromRevision(srcRevision); dstEntry.setCopyFromURL(srcURL); } else { dstEntry.scheduleForAddition(); dstEntry.setKind(SVNNodeKind.FILE); if (!dstEntry.isScheduledForReplacement()) { dstEntry.setRevision(0); } } dstParentArea.saveEntries(false); } else if (srcEntry.isDirectory()) { SVNAdminArea srcArea = wcAccess.open(src, false, 0); srcEntry = srcArea.getEntry(srcArea.getThisDirName(), false); if (dstEntry == null) { dstEntry = dstParentArea.addEntry(dst.getName()); } String srcURL = srcEntry.getURL(); String dstURL = dstParentEntry.getURL(); long srcRevision = srcEntry.getRevision(); String repositoryRootURL = srcEntry.getRepositoryRoot(); dstURL = SVNPathUtil.append(dstURL, SVNEncodingUtil.uriEncode(dst.getName())); SVNAdminArea dstArea = wcAccess.open(dst, true, SVNWCAccess.INFINITE_DEPTH); if (srcEntry.isScheduledForAddition() && srcEntry.isCopied()) { dstEntry.scheduleForAddition(); dstEntry.setKind(SVNNodeKind.DIR); dstParentArea.saveEntries(true); dstArea.updateURL(dstURL, true); dstArea.saveEntries(true); } else if (!srcEntry.isCopied() && !srcEntry.isScheduledForAddition()) { dstEntry.setCopied(true); dstEntry.scheduleForAddition(); dstEntry.setKind(SVNNodeKind.DIR); dstEntry.setCopyFromRevision(srcRevision); dstEntry.setCopyFromURL(srcURL); dstParentArea.saveEntries(true); SVNEntry dstThisEntry = dstArea.getEntry(dstArea.getThisDirName(), false); dstThisEntry.setCopied(true); dstThisEntry.scheduleForAddition(); dstThisEntry.setKind(SVNNodeKind.DIR); dstThisEntry.setCopyFromRevision(srcRevision); dstThisEntry.setURL(dstURL); dstThisEntry.setCopyFromURL(srcURL); dstThisEntry.setRepositoryRoot(repositoryRootURL); updateCopiedDirectory(dstArea, dstArea.getThisDirName(), dstURL, repositoryRootURL, null, -1); dstArea.saveEntries(true); } else { dstParentArea.deleteEntry(dst.getName()); dstParentArea.saveEntries(true); wcAccess.close(); SVNFileUtil.deleteAll(dst, this); SVNFileUtil.copy(src, dst, false, false); myWCClient.doAdd(dst, false, false, false, SVNDepth.INFINITY, false, false); } } try { wcAccess.close(); myWCClient.doDelete(src, true, false); } catch (SVNException e) { } } finally { wcAccess.close(); } } } /** * Copies/moves administrative version control information of a source file * to administrative information of a destination file. For example, if you * have manually copied/moved a source file to a target one (manually means * just in the filesystem, not using version control operations) and then * would like to turn this copying/moving into a complete version control * copy or move operation, use this method that will finish all the work for * you - it will copy/move all the necessary administrative information * (kept in the source <i>.svn</i> directory) to the target <i>.svn</i> * directory. * <p> * In that case when you have your files copied/moved in the filesystem, you * can not perform standard (version control) copying/moving - since the * target already exists and the source may be already deleted. Use this * method to overcome that restriction. * * @param src * a source file path (was copied/moved to <code>dst</code>) * @param dst * a destination file path * @param move * if <span class="javakeyword">true</span> then completes moving * <code>src</code> to <code>dst</code>, otherwise completes * copying <code>src</code> to <code>dst</code> * @throws SVNException * if one of the following is true: * <ul> * <li><code>move = </code><span class="javakeyword">true</span> * and <code>src</code> still exists <li><code>dst</code> does * not exist <li><code>dst</code> is a directory <li><code>src * </code> is a directory <li><code>src</code> is not under * version control <li><code>dst</code> is already under version * control <li>if <code>src</code> is copied but not scheduled * for addition, and SVNKit is not able to locate the copied * directory root for <code>src</code> * </ul> */ public void doVirtualCopy(File src, File dst, boolean move) throws SVNException { SVNFileType srcType = SVNFileType.getType(src); SVNFileType dstType = SVNFileType.getType(dst); String opName = move ? "move" : "copy"; if (move && srcType != SVNFileType.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "Cannot perform 'virtual' {0}: ''{1}'' still exists", new Object[] { opName, src }); SVNErrorManager.error(err, SVNLogType.WC); } if (dstType == SVNFileType.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "Cannot perform 'virtual' {0}: ''{1}'' does not exist", new Object[] { opName, dst }); SVNErrorManager.error(err, SVNLogType.WC); } if (dstType == SVNFileType.DIRECTORY) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Cannot perform 'virtual' {0}: ''{1}'' is a directory", new Object[] { opName, dst }); SVNErrorManager.error(err, SVNLogType.WC); } if (!move && srcType == SVNFileType.DIRECTORY) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Cannot perform 'virtual' {0}: ''{1}'' is a directory", new Object[] { opName, src }); SVNErrorManager.error(err, SVNLogType.WC); } SVNURL srcRepoRoot = null; SVNURL dstRepoRoot = null; boolean versionedDst = false; SVNWCAccess dstAccess = createWCAccess(); try { dstAccess.probeOpen(dst, false, 0); SVNEntry dstEntry = dstAccess.getEntry(dst, false); if (dstEntry != null) { if (!dstEntry.isScheduledForAddition() && !dstEntry.isScheduledForReplacement()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_ATTRIBUTE_INVALID, "Cannot perform 'virtual' {0}: ''{1}'' is scheduled neither for addition nor for replacement", new Object[] { opName, dst }); SVNErrorManager.error(err, SVNLogType.WC); } versionedDst = true; dstRepoRoot = dstEntry.getRepositoryRootURL(); } } finally { dstAccess.close(); } SVNWCAccess srcAccess = createWCAccess(); String cfURL = null; boolean added = false; long cfRevision = -1; try { srcAccess.probeOpen(src, false, 0); SVNEntry srcEntry = srcAccess.getEntry(src, false); if (srcEntry == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", src); SVNErrorManager.error(err, SVNLogType.WC); } srcRepoRoot = srcEntry.getRepositoryRootURL(); if (isCopiedAsAChild(src, srcEntry)) { cfURL = getCopyFromURL(src.getParentFile(), SVNEncodingUtil.uriEncode(src.getName())); cfRevision = getCopyFromRevision(src.getParentFile()); if (cfURL == null || cfRevision < 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "Cannot locate copied directory root for ''{0}''", src); SVNErrorManager.error(err, SVNLogType.WC); } added = false; } else { cfURL = srcEntry.isCopied() ? srcEntry.getCopyFromURL() : srcEntry.getURL(); cfRevision = srcEntry.isCopied() ? srcEntry.getCopyFromRevision() : srcEntry.getRevision(); added = srcEntry.isScheduledForAddition() && !srcEntry.isCopied(); } } finally { srcAccess.close(); } if (added && !versionedDst) { if (move) { myWCClient.doDelete(src, true, false); } myWCClient.doAdd(dst, true, false, false, SVNDepth.EMPTY, false, false); return; } dstAccess = createWCAccess(); srcAccess = createWCAccess(); try { SVNAdminArea dstArea = dstAccess.probeOpen(dst, true, 0); SVNEntry dstEntry = dstAccess.getEntry(dst, false); if (dstEntry != null && !dstEntry.isScheduledForAddition() && !dstEntry.isScheduledForReplacement()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_ATTRIBUTE_INVALID, "Cannot perform 'virtual' {0}: ''{1}'' is scheduled neither for addition nor for replacement", new Object[] { opName, dst }); SVNErrorManager.error(err, SVNLogType.WC); } if (srcRepoRoot != null && dstRepoRoot != null && !dstRepoRoot.equals(srcRepoRoot)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_ATTRIBUTE_INVALID, "Cannot perform 'virtual' {0}: paths belong to different repositories", opName); SVNErrorManager.error(err, SVNLogType.WC); } SVNAdminArea srcArea = srcAccess.probeOpen(src, false, 0); SVNVersionedProperties srcProps = srcArea.getProperties(src.getName()); SVNVersionedProperties srcBaseProps = srcArea.getBaseProperties(src.getName()); SVNVersionedProperties dstProps = dstArea.getProperties(dst.getName()); SVNVersionedProperties dstBaseProps = dstArea.getBaseProperties(dst.getName()); dstBaseProps.removeAll(); srcProps.copyTo(dstProps); srcBaseProps.copyTo(dstBaseProps); dstEntry = dstArea.addEntry(dst.getName()); dstEntry.setCopyFromURL(cfURL); dstEntry.setCopyFromRevision(cfRevision); dstEntry.setCopied(true); dstEntry.setKind(SVNNodeKind.FILE); File baseSrc = srcArea.getBaseFile(src.getName(), false); File baseDst = dstArea.getBaseFile(dst.getName(), false); SVNFileUtil.copyFile(baseSrc, baseDst, false); if (dstEntry.isScheduledForDeletion()) { dstEntry.unschedule(); dstEntry.scheduleForReplacement(); } else if (!dstEntry.isScheduledForReplacement()) { dstEntry.unschedule(); dstEntry.scheduleForAddition(); } dstArea.saveEntries(false); SVNLog log = dstArea.getLog(); dstArea.saveVersionedProperties(log, true); log.save(); dstArea.runLogs(); SVNEvent event = SVNEventFactory.createSVNEvent(dst, SVNNodeKind.FILE, null, SVNRepository.INVALID_REVISION, SVNEventAction.COPY, null, null, null); dispatchEvent(event); } finally { srcAccess.close(); dstAccess.close(); } if (move) { myWCClient.doDelete(src, true, false); } } private boolean isCopiedAsAChild(File path, SVNEntry entry) throws SVNException { if (!entry.isScheduledForAddition() && entry.isCopied()) { return true; } if (entry.isScheduledForDeletion() && path != null) { return getCopyFromURL(path.getParentFile(), SVNEncodingUtil.uriEncode(entry.getName())) != null; } return false; } private void updateCopiedDirectory(SVNAdminArea dir, String name, String newURL, String reposRootURL, String copyFromURL, long copyFromRevision) throws SVNException { SVNWCAccess wcAccess = dir.getWCAccess(); SVNEntry entry = dir.getEntry(name, true); if (entry != null) { entry.setCopied(true); if (newURL != null) { entry.setURL(newURL); } entry.setRepositoryRoot(reposRootURL); if (entry.isFile()) { if (dir.getWCProperties(name) != null) { dir.getWCProperties(name).removeAll(); dir.saveWCProperties(false); } if (copyFromURL != null) { entry.setCopyFromURL(copyFromURL); entry.setCopyFromRevision(copyFromRevision); } } boolean deleted = false; if (entry.isDeleted() && newURL != null) { deleted = true; entry.setDeleted(false); entry.scheduleForDeletion(); if (entry.isDirectory()) { entry.setKind(SVNNodeKind.FILE); } } if (entry.getLockToken() != null && newURL != null) { entry.setLockToken(null); entry.setLockOwner(null); entry.setLockComment(null); entry.setLockCreationDate(null); } if (!dir.getThisDirName().equals(name) && entry.isDirectory() && !deleted) { SVNAdminArea childDir = wcAccess.retrieve(dir.getFile(name)); if (childDir != null) { String childCopyFromURL = copyFromURL == null ? null : SVNPathUtil.append(copyFromURL, SVNEncodingUtil.uriEncode(entry.getName())); updateCopiedDirectory(childDir, childDir.getThisDirName(), newURL, reposRootURL, childCopyFromURL, copyFromRevision); } } else if (dir.getThisDirName().equals(name)) { dir.getWCProperties(dir.getThisDirName()).removeAll(); dir.saveWCProperties(false); if (copyFromURL != null) { entry.setCopyFromURL(copyFromURL); entry.setCopyFromRevision(copyFromRevision); } for (Iterator ents = dir.entries(true); ents.hasNext();) { SVNEntry childEntry = (SVNEntry) ents.next(); if (dir.getThisDirName().equals(childEntry.getName())) { continue; } String childCopyFromURL = copyFromURL == null ? null : SVNPathUtil.append(copyFromURL, SVNEncodingUtil.uriEncode(childEntry.getName())); String newChildURL = newURL == null ? null : SVNPathUtil.append(newURL, SVNEncodingUtil.uriEncode(childEntry.getName())); updateCopiedDirectory(dir, childEntry.getName(), newChildURL, reposRootURL, childCopyFromURL, copyFromRevision); } dir.saveEntries(false); } } } private String getCopyFromURL(File path, String urlTail) throws SVNException { if (path == null) { return null; } SVNWCAccess wcAccess = createWCAccess(); try { wcAccess.probeOpen(path, false, 0); } catch (SVNException e) { wcAccess.close(); return null; } try { SVNEntry entry = wcAccess.getEntry(path, false); if (entry == null) { return null; } String cfURL = entry.getCopyFromURL(); if (cfURL != null) { return SVNPathUtil.append(cfURL, urlTail); } urlTail = SVNPathUtil.append(SVNEncodingUtil.uriEncode(path.getName()), urlTail); path = path.getParentFile(); return getCopyFromURL(path, urlTail); } finally { wcAccess.close(); } } private long getCopyFromRevision(File path) throws SVNException { if (path == null) { return -1; } SVNWCAccess wcAccess = createWCAccess(); try { wcAccess.probeOpen(path, false, 0); } catch (SVNException e) { wcAccess.close(); return -1; } try { SVNEntry entry = wcAccess.getEntry(path, false); if (entry == null) { return -1; } long rev = entry.getCopyFromRevision(); if (rev >= 0) { return rev; } path = path.getParentFile(); return getCopyFromRevision(path); } finally { wcAccess.close(); } } private static boolean isVersionedFile(File file) { SVNWCAccess wcAccess = SVNWCAccess.newInstance(null); try { SVNAdminArea area = wcAccess.probeOpen(file, false, 0); if (area.getEntry(area.getThisDirName(), false) == null) { return false; } SVNFileType type = SVNFileType.getType(file); if (type.isFile() || type == SVNFileType.NONE) { return area.getEntry(file.getName(), false) != null; } else if (type != SVNFileType.NONE && !area.getRoot().equals(file)) { return false; } return true; } catch (SVNException e) { return false; } finally { try { wcAccess.close(); } catch (SVNException svne) { } } } }