0001: /*
0002: * ====================================================================
0003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
0004: *
0005: * This software is licensed as described in the file COPYING, which
0006: * you should have received as part of this distribution. The terms
0007: * are also available at http://svnkit.com/license.html
0008: * If newer versions of this license are posted there, you may use a
0009: * newer version instead, at your option.
0010: * ====================================================================
0011: */
0012: package org.tmatesoft.svn.core.wc;
0013:
0014: import java.io.File;
0015: import java.util.Iterator;
0016:
0017: import org.tmatesoft.svn.core.SVNErrorCode;
0018: import org.tmatesoft.svn.core.SVNErrorMessage;
0019: import org.tmatesoft.svn.core.SVNException;
0020: import org.tmatesoft.svn.core.SVNNodeKind;
0021: import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
0022: import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
0023: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
0024: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
0025: import org.tmatesoft.svn.core.internal.wc.SVNFileType;
0026: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
0027: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
0028: import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
0029: import org.tmatesoft.svn.core.internal.wc.admin.SVNLog;
0030: import org.tmatesoft.svn.core.internal.wc.admin.SVNVersionedProperties;
0031: import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
0032:
0033: /**
0034: * The <b>SVNMoveClient</b> provides an extra client-side functionality over
0035: * standard (i.e. compatible with the SVN command line client) move
0036: * operations. This class helps to overcome the SVN limitations regarding
0037: * move operations. Using <b>SVNMoveClient</b> you can easily:
0038: * <ul>
0039: * <li>move versioned items to other versioned ones
0040: * within the same Working Copy, what even allows to replace items
0041: * scheduled for deletion, or those that are missing but are still under
0042: * version control and have a node kind different from the node kind of the
0043: * source (!);
0044: * <li>move versioned items belonging to one Working Copy to versioned items
0045: * that belong to absolutely different Working Copy;
0046: * <li>move versioned items to unversioned ones;
0047: * <li>move unversioned items to versioned ones;
0048: * <li>move unversioned items to unversioned ones;
0049: * <li>revert any of the kinds of moving listed above;
0050: * <li>complete a copy/move operation for a file, that is if you have
0051: * manually copied/moved a versioned file to an unversioned file in a Working
0052: * copy, you can run a 'virtual' copy/move on these files to copy/move
0053: * all the necessary administrative version control information.
0054: * </ul>
0055: *
0056: * @version 1.1.1
0057: * @author TMate Software Ltd.
0058: */
0059: public class SVNMoveClient extends SVNBasicClient {
0060:
0061: private SVNWCClient myWCClient;
0062:
0063: /**
0064: * Constructs and initializes an <b>SVNMoveClient</b> object
0065: * with the specified run-time configuration and authentication
0066: * drivers.
0067: *
0068: * <p>
0069: * If <code>options</code> is <span class="javakeyword">null</span>,
0070: * then this <b>SVNMoveClient</b> will be using a default run-time
0071: * configuration driver which takes client-side settings from the
0072: * default SVN's run-time configuration area but is not able to
0073: * change those settings (read more on {@link ISVNOptions} and {@link SVNWCUtil}).
0074: *
0075: * <p>
0076: * If <code>authManager</code> is <span class="javakeyword">null</span>,
0077: * then this <b>SVNMoveClient</b> will be using a default authentication
0078: * and network layers driver (see {@link SVNWCUtil#createDefaultAuthenticationManager()})
0079: * which uses server-side settings and auth storage from the
0080: * default SVN's run-time configuration area (or system properties
0081: * if that area is not found).
0082: *
0083: * @param authManager an authentication and network layers driver
0084: * @param options a run-time configuration options driver
0085: */
0086: public SVNMoveClient(ISVNAuthenticationManager authManager,
0087: ISVNOptions options) {
0088: super (authManager, options);
0089: myWCClient = new SVNWCClient(authManager, options);
0090: }
0091:
0092: public SVNMoveClient(ISVNRepositoryPool repositoryPool,
0093: ISVNOptions options) {
0094: super (repositoryPool, options);
0095: myWCClient = new SVNWCClient(repositoryPool, options);
0096: }
0097:
0098: /**
0099: * Moves a source item to a destination one.
0100: *
0101: * <p>
0102: * <code>dst</code> should not exist. Furher it's considered to be versioned if
0103: * its parent directory is under version control, otherwise <code>dst</code>
0104: * is considered to be unversioned.
0105: *
0106: * <p>
0107: * If both <code>src</code> and <code>dst</code> are unversioned, then simply
0108: * moves <code>src</code> to <code>dst</code> in the filesystem.
0109: *
0110: * <p>
0111: * If <code>src</code> is versioned but <code>dst</code> is not, then
0112: * exports <code>src</code> to <code>dst</code> in the filesystem and
0113: * removes <code>src</code> from version control.
0114: *
0115: * <p>
0116: * If <code>dst</code> is versioned but <code>src</code> is not, then
0117: * moves <code>src</code> to <code>dst</code> (even if <code>dst</code>
0118: * is scheduled for deletion).
0119: *
0120: * <p>
0121: * If both <code>src</code> and <code>dst</code> are versioned and located
0122: * within the same Working Copy, then moves <code>src</code> to
0123: * <code>dst</code> (even if <code>dst</code> is scheduled for deletion),
0124: * or tries to replace <code>dst</code> with <code>src</code> if the former
0125: * is missing and has a node kind different from the node kind of the source.
0126: * If <code>src</code> is scheduled for addition with history,
0127: * <code>dst</code> will be set the same ancestor URL and revision from which
0128: * the source was copied. If <code>src</code> and <code>dst</code> are located in
0129: * different Working Copies, then this method copies <code>src</code> to
0130: * <code>dst</code>, tries to put the latter under version control and
0131: * finally removes <code>src</code>.
0132: *
0133: * @param src a source path
0134: * @param dst a destination path
0135: * @throws SVNException if one of the following is true:
0136: * <ul>
0137: * <li><code>dst</code> already exists
0138: * <li><code>src</code> does not exist
0139: * </ul>
0140: */
0141: public void doMove(File src, File dst) throws SVNException {
0142: if (dst.exists()) {
0143: SVNErrorMessage err = SVNErrorMessage.create(
0144: SVNErrorCode.ENTRY_EXISTS,
0145: "File ''{0}'' already exists", dst);
0146: SVNErrorManager.error(err);
0147: } else if (!src.exists()) {
0148: SVNErrorMessage err = SVNErrorMessage.create(
0149: SVNErrorCode.NODE_UNKNOWN_KIND,
0150: "Path ''{0}'' does not exist", src);
0151: SVNErrorManager.error(err);
0152: }
0153: // src considered as unversioned when it is not versioned
0154: boolean srcIsVersioned = isVersionedFile(src);
0155: // dst is considered as unversioned when its parent is not versioned.
0156: boolean dstParentIsVersioned = isVersionedFile(dst
0157: .getParentFile());
0158:
0159: if (!srcIsVersioned && !dstParentIsVersioned) {
0160: // world:world
0161: SVNFileUtil.rename(src, dst);
0162: } else if (!dstParentIsVersioned) {
0163: // wc:world
0164: // 1. export to world
0165: SVNFileUtil.copy(src, dst, false, false);
0166:
0167: // 2. delete in wc.
0168: myWCClient.doDelete(src, true, false);
0169: } else if (!srcIsVersioned) {
0170: // world:wc (add, if dst is 'deleted' it will be replaced)
0171: SVNFileUtil.rename(src, dst);
0172: // do we have to add it if it was unversioned?
0173: //myWCClient.doAdd(dst, false, false, false, true, false);
0174: } else {
0175: // wc:wc.
0176:
0177: SVNWCAccess wcAccess = createWCAccess();
0178: File srcParent = src.getParentFile();
0179: File dstParent = dst.getParentFile();
0180: SVNAdminArea srcParentArea = null;
0181: SVNAdminArea dstParentArea = null;
0182: try {
0183: if (srcParent.equals(dstParent)) {
0184: wcAccess.closeAdminArea(srcParent);
0185: srcParentArea = dstParentArea = wcAccess.open(
0186: srcParent, true, 0);
0187: } else {
0188: srcParentArea = wcAccess.open(srcParent, false, 0);
0189: dstParentArea = wcAccess.open(dstParent, true, 0);
0190: }
0191:
0192: SVNEntry srcEntry = srcParentArea.getEntry(src
0193: .getName(), true);
0194: SVNEntry dstEntry = dstParentArea.getEntry(dst
0195: .getName(), true);
0196:
0197: File srcWCRoot = SVNWCUtil
0198: .getWorkingCopyRoot(src, true);
0199: File dstWCRoot = SVNWCUtil
0200: .getWorkingCopyRoot(dst, true);
0201: boolean sameWC = srcWCRoot != null
0202: && srcWCRoot.equals(dstWCRoot);
0203: if (sameWC
0204: && dstEntry != null
0205: && (dstEntry.isScheduledForDeletion() || dstEntry
0206: .getKind() != srcEntry.getKind())) {
0207: wcAccess.close();
0208: if (srcEntry.getKind() == dstEntry.getKind()
0209: && srcEntry.getSchedule() == null
0210: && srcEntry.isFile()) {
0211: // make normal move to keep history (R+).
0212: SVNCopyClient copyClient = new SVNCopyClient(
0213: (ISVNAuthenticationManager) null, null);
0214: copyClient.doCopy(src, SVNRevision.WORKING,
0215: dst, true, true);
0216: return;
0217: }
0218: // attempt replace.
0219: SVNFileUtil.copy(src, dst, false, false);
0220: try {
0221: myWCClient.doAdd(dst, false, false, false,
0222: true, false);
0223: } catch (SVNException e) {
0224: // will be thrown on obstruction.
0225: }
0226: myWCClient.doDelete(src, true, false);
0227: return;
0228: }
0229:
0230: // 2. do manual copy of the file or directory
0231: SVNFileUtil.copy(src, dst, false, sameWC);
0232:
0233: // 3. update dst dir and dst entry in parent.
0234: if (!sameWC) {
0235: // just add dst (at least try to add, files already there).
0236: wcAccess.close();
0237: try {
0238: myWCClient.doAdd(dst, false, false, false,
0239: true, false);
0240: } catch (SVNException e) {
0241: // obstruction
0242: }
0243: } else if (srcEntry.isFile()) {
0244:
0245: if (dstEntry == null) {
0246: dstEntry = dstParentArea
0247: .addEntry(dst.getName());
0248: }
0249:
0250: String srcURL = srcEntry.getURL();
0251: String srcCFURL = srcEntry.getCopyFromURL();
0252: long srcRevision = srcEntry.getRevision();
0253: long srcCFRevision = srcEntry.getCopyFromRevision();
0254: // copy props!
0255: SVNVersionedProperties srcProps = srcParentArea
0256: .getProperties(src.getName());
0257: SVNVersionedProperties dstProps = dstParentArea
0258: .getProperties(dst.getName());
0259: srcProps.copyTo(dstProps);
0260: File srcBaseFile = srcParentArea.getBaseFile(src
0261: .getName(), false);
0262: File dstBaseFile = dstParentArea.getBaseFile(dst
0263: .getName(), false);
0264: if (srcBaseFile.isFile()) {
0265: SVNFileUtil.copy(srcBaseFile, dstBaseFile,
0266: false, false);
0267: }
0268:
0269: if (srcEntry.isScheduledForAddition()
0270: && srcEntry.isCopied()) {
0271: dstEntry.scheduleForAddition();
0272: dstEntry.setCopyFromRevision(srcCFRevision);
0273: dstEntry.setCopyFromURL(srcCFURL);
0274: dstEntry.setKind(SVNNodeKind.FILE);
0275: dstEntry.setRevision(srcRevision);
0276: dstEntry.setCopied(true);
0277: } else if (!srcEntry.isCopied()
0278: && !srcEntry.isScheduledForAddition()) {
0279: dstEntry.setCopied(true);
0280: dstEntry.scheduleForAddition();
0281: dstEntry.setKind(SVNNodeKind.FILE);
0282: dstEntry.setCopyFromRevision(srcRevision);
0283: dstEntry.setCopyFromURL(srcURL);
0284: } else {
0285: dstEntry.scheduleForAddition();
0286: dstEntry.setKind(SVNNodeKind.FILE);
0287: if (!dstEntry.isScheduledForReplacement()) {
0288: dstEntry.setRevision(0);
0289: }
0290: }
0291:
0292: SVNLog log = dstParentArea.getLog();
0293: dstParentArea.saveEntries(false);
0294: dstParentArea.saveVersionedProperties(log, true);
0295: log.save();
0296: dstParentArea.runLogs();
0297: } else if (srcEntry.isDirectory()) {
0298: SVNAdminArea srcArea = wcAccess.open(src, false, 0);
0299: srcEntry = srcArea.getEntry(srcArea
0300: .getThisDirName(), false);
0301: if (dstEntry == null) {
0302: dstEntry = dstParentArea
0303: .addEntry(dst.getName());
0304: }
0305: SVNAdminArea dstArea = wcAccess.open(dst, true,
0306: SVNWCAccess.INFINITE_DEPTH);
0307:
0308: SVNVersionedProperties srcProps = srcArea
0309: .getProperties(srcArea.getThisDirName());
0310: SVNVersionedProperties dstProps = dstArea
0311: .getProperties(dstArea.getThisDirName());
0312:
0313: SVNEntry dstParentEntry = dstParentArea.getEntry(
0314: dstParentArea.getThisDirName(), false);
0315: String srcURL = srcEntry.getURL();
0316: String srcCFURL = srcEntry.getCopyFromURL();
0317: String dstURL = dstParentEntry.getURL();
0318: String repositoryRootURL = dstParentEntry
0319: .getRepositoryRoot();
0320: long srcRevision = srcEntry.getRevision();
0321: long srcCFRevision = srcEntry.getCopyFromRevision();
0322:
0323: dstURL = SVNPathUtil.append(dstURL, SVNEncodingUtil
0324: .uriEncode(dst.getName()));
0325: if (srcEntry.isScheduledForAddition()
0326: && srcEntry.isCopied()) {
0327: srcProps.copyTo(dstProps);
0328: dstEntry.scheduleForAddition();
0329: dstEntry.setKind(SVNNodeKind.DIR);
0330: dstEntry.setCopied(true);
0331: dstEntry.setCopyFromRevision(srcCFRevision);
0332: dstEntry.setCopyFromURL(srcCFURL);
0333:
0334: SVNEntry dstThisEntry = dstArea.getEntry(
0335: dstArea.getThisDirName(), false);
0336: dstThisEntry.scheduleForAddition();
0337: dstThisEntry.setKind(SVNNodeKind.DIR);
0338: dstThisEntry.setCopyFromRevision(srcCFRevision);
0339: dstThisEntry.setCopyFromURL(srcCFURL);
0340: dstThisEntry.setRevision(srcRevision);
0341: dstThisEntry.setCopied(true);
0342:
0343: SVNLog log = dstArea.getLog();
0344: dstArea.saveVersionedProperties(log, true);
0345: dstParentArea.saveEntries(false);
0346: log.save();
0347: dstArea.runLogs();
0348:
0349: // update URL in children.
0350: dstArea.updateURL(dstURL, true);
0351: dstParentArea.saveEntries(true);
0352: } else if (!srcEntry.isCopied()
0353: && !srcEntry.isScheduledForAddition()) {
0354: // versioned (deleted, replaced, or normal).
0355: srcProps.copyTo(dstProps);
0356: dstEntry.scheduleForAddition();
0357: dstEntry.setKind(SVNNodeKind.DIR);
0358: dstEntry.setCopied(true);
0359: dstEntry.setCopyFromRevision(srcRevision);
0360: dstEntry.setCopyFromURL(srcURL);
0361:
0362: // update URL, CF-URL and CF-REV in children.
0363: SVNEntry dstThisEntry = dstArea.getEntry(
0364: dstArea.getThisDirName(), false);
0365: dstThisEntry.scheduleForAddition();
0366: dstThisEntry.setKind(SVNNodeKind.DIR);
0367: dstThisEntry.setCopied(true);
0368: dstThisEntry.scheduleForAddition();
0369: dstThisEntry.setKind(SVNNodeKind.DIR);
0370: dstThisEntry.setCopyFromRevision(srcRevision);
0371: dstThisEntry.setCopyFromURL(srcURL);
0372: dstThisEntry.setURL(dstURL);
0373: dstThisEntry
0374: .setRepositoryRoot(repositoryRootURL);
0375:
0376: SVNLog log = dstArea.getLog();
0377: dstArea.saveVersionedProperties(log, true);
0378: dstArea.saveEntries(false);
0379: log.save();
0380: dstArea.runLogs();
0381:
0382: updateCopiedDirectory(dstArea, dstArea
0383: .getThisDirName(), dstURL,
0384: repositoryRootURL, null, -1);
0385: dstArea.saveEntries(true);
0386: dstParentArea.saveEntries(true);
0387:
0388: } else {
0389: // unversioned entry (copied or added)
0390: dstParentArea.deleteEntry(dst.getName());
0391: dstParentArea.saveEntries(true);
0392: SVNFileUtil.deleteAll(dst, this );
0393: SVNFileUtil.copy(src, dst, false, false);
0394: wcAccess.close();
0395: myWCClient.doAdd(dst, false, false, false,
0396: true, false);
0397: }
0398: }
0399: // now delete src (if it is not the same as dst :))
0400: try {
0401: wcAccess.close();
0402: myWCClient.doDelete(src, true, false);
0403: } catch (SVNException e) {
0404: //
0405: }
0406: } finally {
0407: wcAccess.close();
0408: }
0409: }
0410: }
0411:
0412: private void updateCopiedDirectory(SVNAdminArea dir, String name,
0413: String newURL, String reposRootURL, String copyFromURL,
0414: long copyFromRevision) throws SVNException {
0415: SVNWCAccess wcAccess = dir.getWCAccess();
0416: SVNEntry entry = dir.getEntry(name, true);
0417: if (entry != null) {
0418: entry.setCopied(true);
0419: if (newURL != null) {
0420: entry.setURL(newURL);
0421: }
0422: entry.setRepositoryRoot(reposRootURL);
0423: if (entry.isFile()) {
0424: if (dir.getWCProperties(name) != null) {
0425: dir.getWCProperties(name).removeAll();
0426: dir.saveWCProperties(false);
0427: }
0428: if (copyFromURL != null) {
0429: entry.setCopyFromURL(copyFromURL);
0430: entry.setCopyFromRevision(copyFromRevision);
0431: }
0432: }
0433: boolean deleted = false;
0434: if (entry.isDeleted() && newURL != null) {
0435: // convert to scheduled for deletion.
0436: deleted = true;
0437: entry.setDeleted(false);
0438: entry.scheduleForDeletion();
0439: if (entry.isDirectory()) {
0440: entry.setKind(SVNNodeKind.FILE);
0441: }
0442: }
0443: if (entry.getLockToken() != null && newURL != null) {
0444: entry.setLockToken(null);
0445: entry.setLockOwner(null);
0446: entry.setLockComment(null);
0447: entry.setLockCreationDate(null);
0448: }
0449: if (!dir.getThisDirName().equals(name)
0450: && entry.isDirectory() && !deleted) {
0451: SVNAdminArea childDir = wcAccess.retrieve(dir
0452: .getFile(name));
0453: if (childDir != null) {
0454: String childCopyFromURL = copyFromURL == null ? null
0455: : SVNPathUtil.append(copyFromURL,
0456: SVNEncodingUtil.uriEncode(entry
0457: .getName()));
0458: updateCopiedDirectory(childDir, childDir
0459: .getThisDirName(), newURL, reposRootURL,
0460: childCopyFromURL, copyFromRevision);
0461: }
0462: } else if (dir.getThisDirName().equals(name)) {
0463: dir.getWCProperties(dir.getThisDirName()).removeAll();
0464: dir.saveWCProperties(false);
0465: if (copyFromURL != null) {
0466: entry.setCopyFromURL(copyFromURL);
0467: entry.setCopyFromRevision(copyFromRevision);
0468: }
0469: for (Iterator ents = dir.entries(true); ents.hasNext();) {
0470: SVNEntry childEntry = (SVNEntry) ents.next();
0471: if (dir.getThisDirName().equals(
0472: childEntry.getName())) {
0473: continue;
0474: }
0475: String childCopyFromURL = copyFromURL == null ? null
0476: : SVNPathUtil.append(copyFromURL,
0477: SVNEncodingUtil
0478: .uriEncode(childEntry
0479: .getName()));
0480: String newChildURL = newURL == null ? null
0481: : SVNPathUtil.append(newURL,
0482: SVNEncodingUtil
0483: .uriEncode(childEntry
0484: .getName()));
0485: updateCopiedDirectory(dir, childEntry.getName(),
0486: newChildURL, reposRootURL,
0487: childCopyFromURL, copyFromRevision);
0488: }
0489: dir.saveEntries(false);
0490: }
0491: }
0492: }
0493:
0494: /**
0495: * Reverts a previous move operation back. Provided in pair with {@link #doMove(File, File) doMove()}
0496: * and used to roll back move operations. In this case <code>src</code> is
0497: * considered to be the target of the previsous move operation, and <code>dst</code>
0498: * is regarded to be the source of that same operation which have been moved
0499: * to <code>src</code> and now is to be restored.
0500: *
0501: * <p>
0502: * <code>dst</code> could exist in that case if it has been a WC directory
0503: * that was scheduled for deletion during the previous move operation. Furher
0504: * <code>dst</code> is considered to be versioned if its parent directory is
0505: * under version control, otherwise <code>dst</code> is considered to be unversioned.
0506: *
0507: * <p>
0508: * If both <code>src</code> and <code>dst</code> are unversioned, then simply
0509: * moves <code>src</code> back to <code>dst</code> in the filesystem.
0510: *
0511: * <p>
0512: * If <code>src</code> is versioned but <code>dst</code> is not, then
0513: * unmoves <code>src</code> to <code>dst</code> in the filesystem and
0514: * removes <code>src</code> from version control.
0515: *
0516: * <p>
0517: * If <code>dst</code> is versioned but <code>src</code> is not, then
0518: * first tries to make a revert on <code>dst</code> - if it has not been committed
0519: * yet, it will be simply reverted. However in the case <code>dst</code> has been already removed
0520: * from the repository, <code>src</code> will be copied back to <code>dst</code>
0521: * and scheduled for addition. Then <code>src</code> is removed from the filesystem.
0522: *
0523: * <p>
0524: * If both <code>src</code> and <code>dst</code> are versioned then the
0525: * following situations are possible:
0526: * <ul>
0527: * <li>If <code>dst</code> is still scheduled for deletion, then it is
0528: * reverted back and <code>src</code> is scheduled for deletion.
0529: * <li>in the case if <code>dst</code> exists but is not scheduled for
0530: * deletion, <code>src</code> is cleanly exported to <code>dst</code> and
0531: * removed from version control.
0532: * <li>if <code>dst</code> and <code>src</code> are from different repositories
0533: * (appear to be in different Working Copies), then <code>src</code> is copied
0534: * to <code>dst</code> (with scheduling <code>dst</code> for addition, but not
0535: * with history since copying is made in the filesystem only) and removed from
0536: * version control.
0537: * <li>if both <code>dst</code> and <code>src</code> are in the same
0538: * repository (appear to be located in the same Working Copy) and:
0539: * <ul style="list-style-type: lower-alpha">
0540: * <li>if <code>src</code> is scheduled for addition with history, then
0541: * copies <code>src</code> to <code>dst</code> specifying the source
0542: * ancestor's URL and revision (i.e. the ancestor of the source is the
0543: * ancestor of the destination);
0544: * <li>if <code>src</code> is already under version control, then
0545: * copies <code>src</code> to <code>dst</code> specifying the source
0546: * URL and revision as the ancestor (i.e. <code>src</code> itself is the
0547: * ancestor of <code>dst</code>);
0548: * <li>if <code>src</code> is just scheduled for addition (without history),
0549: * then simply copies <code>src</code> to <code>dst</code> (only in the filesystem,
0550: * without history) and schedules <code>dst</code> for addition;
0551: * </ul>
0552: * then <code>src</code> is removed from version control.
0553: * </ul>
0554: *
0555: * @param src a source path
0556: * @param dst a destination path
0557: * @throws SVNException if <code>src</code> does not exist
0558: *
0559: */
0560: // move that considered as move undo.
0561: public void undoMove(File src, File dst) throws SVNException {
0562: // dst could exists, if it is deleted directory.
0563: if (!src.exists()) {
0564: SVNErrorMessage err = SVNErrorMessage.create(
0565: SVNErrorCode.NODE_UNKNOWN_KIND,
0566: "Path ''{0}'' does not exist", src);
0567: SVNErrorManager.error(err);
0568: }
0569: // src considered as unversioned when it is not versioned
0570: boolean srcIsVersioned = isVersionedFile(src);
0571: // dst is considered as unversioned when its parent is not versioned.
0572: boolean dstParentIsVersioned = isVersionedFile(dst
0573: .getParentFile());
0574:
0575: if (!srcIsVersioned && !dstParentIsVersioned) {
0576: // world:world
0577: SVNFileUtil.rename(src, dst);
0578: } else if (!dstParentIsVersioned) {
0579: // wc:world
0580: // 1. export to world
0581: SVNFileUtil.copy(src, dst, false, false);
0582:
0583: // 2. delete in wc.
0584: myWCClient.doDelete(src, true, false);
0585: } else if (!srcIsVersioned) {
0586: // world:wc (add, if dst is 'deleted' it will be replaced)
0587: SVNFileUtil.rename(src, dst);
0588: // dst should probably be deleted, in this case - revert it
0589: SVNWCAccess dstAccess = createWCAccess();
0590: boolean revert = false;
0591: try {
0592: dstAccess.probeOpen(dst, false, 0);
0593: SVNEntry dstEntry = dstAccess.getEntry(dst, false);
0594: revert = dstEntry != null
0595: && dstEntry.isScheduledForDeletion();
0596: } catch (SVNException e) {
0597: } finally {
0598: dstAccess.close();
0599: }
0600: if (revert) {
0601: myWCClient.doRevert(dst, true);
0602: } else {
0603: // should we do this? there is no old source, may be rename is enough.
0604: // myWCClient.doAdd(dst, false, false, false, true, false);
0605: }
0606: } else {
0607: // wc:wc.
0608: SVNWCAccess wcAccess = createWCAccess();
0609: File srcParent = src.getParentFile();
0610: File dstParent = dst.getParentFile();
0611: SVNAdminArea srcParentArea = null;
0612: SVNAdminArea dstParentArea = null;
0613: try {
0614: if (srcParent.equals(dstParent)) {
0615: wcAccess.closeAdminArea(srcParent);
0616: srcParentArea = dstParentArea = wcAccess.open(
0617: srcParent, true, 0);
0618: } else {
0619: srcParentArea = wcAccess.open(srcParent, false, 0);
0620: dstParentArea = wcAccess.open(dstParent, true, 0);
0621: }
0622:
0623: SVNEntry srcEntry = srcParentArea.getEntry(src
0624: .getName(), true);
0625: SVNEntry dstEntry = dstParentArea.getEntry(dst
0626: .getName(), true);
0627: if (dstEntry != null
0628: && dstEntry.isScheduledForDeletion()) {
0629: wcAccess.close();
0630: // clear undo.
0631: myWCClient.doRevert(dst, true);
0632: myWCClient.doDelete(src, true, false);
0633: return;
0634: }
0635: SVNEntry dstParentEntry = wcAccess.getEntry(dstParent,
0636: false);
0637:
0638: File srcWCRoot = SVNWCUtil
0639: .getWorkingCopyRoot(src, true);
0640: File dstWCRoot = SVNWCUtil
0641: .getWorkingCopyRoot(dst, true);
0642: boolean sameWC = srcWCRoot != null
0643: && srcWCRoot.equals(dstWCRoot);
0644:
0645: SVNFileUtil.copy(src, dst, false, sameWC);
0646:
0647: // obstruction assertion.
0648: if (dstEntry != null
0649: && dstEntry.getKind() != srcEntry.getKind()) {
0650: // ops have no sence->target is obstructed, just export src to
0651: // dst and delete src.
0652: wcAccess.close();
0653: myWCClient.doDelete(src, true, false);
0654: return;
0655: }
0656: if (!sameWC) {
0657: // just add dst (at least try to add, files already there).
0658: wcAccess.close();
0659: try {
0660: myWCClient.doAdd(dst, false, false, false,
0661: true, false);
0662: } catch (SVNException e) {
0663: // obstruction
0664: }
0665: } else if (srcEntry.isFile()) {
0666: if (dstEntry == null) {
0667: dstEntry = dstParentArea
0668: .addEntry(dst.getName());
0669: }
0670:
0671: String srcURL = srcEntry.getURL();
0672: String srcCFURL = srcEntry.getCopyFromURL();
0673: long srcRevision = srcEntry.getRevision();
0674: long srcCFRevision = srcEntry.getCopyFromRevision();
0675:
0676: if (srcEntry.isScheduledForAddition()
0677: && srcEntry.isCopied()) {
0678: dstEntry.scheduleForAddition();
0679: dstEntry.setCopyFromRevision(srcCFRevision);
0680: dstEntry.setCopyFromURL(srcCFURL);
0681: dstEntry.setKind(SVNNodeKind.FILE);
0682: dstEntry.setRevision(srcRevision);
0683: dstEntry.setCopied(true);
0684: } else if (!srcEntry.isCopied()
0685: && !srcEntry.isScheduledForAddition()) {
0686: dstEntry.setCopied(true);
0687: dstEntry.scheduleForAddition();
0688: dstEntry.setKind(SVNNodeKind.FILE);
0689: dstEntry.setCopyFromRevision(srcRevision);
0690: dstEntry.setCopyFromURL(srcURL);
0691: } else {
0692: dstEntry.scheduleForAddition();
0693: dstEntry.setKind(SVNNodeKind.FILE);
0694: if (!dstEntry.isScheduledForReplacement()) {
0695: dstEntry.setRevision(0);
0696: }
0697: }
0698: dstParentArea.saveEntries(false);
0699: } else if (srcEntry.isDirectory()) {
0700: SVNAdminArea srcArea = wcAccess.open(src, false, 0);
0701: srcEntry = srcArea.getEntry(srcArea
0702: .getThisDirName(), false);
0703: if (dstEntry == null) {
0704: dstEntry = dstParentArea
0705: .addEntry(dst.getName());
0706: }
0707:
0708: String srcURL = srcEntry.getURL();
0709: String dstURL = dstParentEntry.getURL();
0710: long srcRevision = srcEntry.getRevision();
0711: String repositoryRootURL = srcEntry
0712: .getRepositoryRoot();
0713:
0714: dstURL = SVNPathUtil.append(dstURL, SVNEncodingUtil
0715: .uriEncode(dst.getName()));
0716:
0717: SVNAdminArea dstArea = wcAccess.open(dst, true,
0718: SVNWCAccess.INFINITE_DEPTH);
0719:
0720: if (srcEntry.isScheduledForAddition()
0721: && srcEntry.isCopied()) {
0722: dstEntry.scheduleForAddition();
0723: dstEntry.setKind(SVNNodeKind.DIR);
0724: dstParentArea.saveEntries(true);
0725: // update URL in children.
0726: dstArea.updateURL(dstURL, true);
0727: dstArea.saveEntries(true);
0728: } else if (!srcEntry.isCopied()
0729: && !srcEntry.isScheduledForAddition()) {
0730: dstEntry.setCopied(true);
0731: dstEntry.scheduleForAddition();
0732: dstEntry.setKind(SVNNodeKind.DIR);
0733: dstEntry.setCopyFromRevision(srcRevision);
0734: dstEntry.setCopyFromURL(srcURL);
0735:
0736: dstParentArea.saveEntries(true);
0737:
0738: SVNEntry dstThisEntry = dstArea.getEntry(
0739: dstArea.getThisDirName(), false);
0740: dstThisEntry.setCopied(true);
0741: dstThisEntry.scheduleForAddition();
0742: dstThisEntry.setKind(SVNNodeKind.DIR);
0743: dstThisEntry.setCopyFromRevision(srcRevision);
0744: dstThisEntry.setURL(dstURL);
0745: dstThisEntry.setCopyFromURL(srcURL);
0746: dstThisEntry
0747: .setRepositoryRoot(repositoryRootURL);
0748:
0749: updateCopiedDirectory(dstArea, dstArea
0750: .getThisDirName(), dstURL,
0751: repositoryRootURL, null, -1);
0752: dstArea.saveEntries(true);
0753: } else {
0754: // replay
0755: dstParentArea.deleteEntry(dst.getName());
0756: dstParentArea.saveEntries(true);
0757: wcAccess.close();
0758: SVNFileUtil.deleteAll(dst, this );
0759: SVNFileUtil.copy(src, dst, false, false);
0760: myWCClient.doAdd(dst, false, false, false,
0761: true, false);
0762: }
0763: }
0764: // now delete src.
0765: try {
0766: wcAccess.close();
0767: myWCClient.doDelete(src, true, false);
0768: } catch (SVNException e) {
0769: //
0770: }
0771: } finally {
0772: wcAccess.close();
0773: }
0774: }
0775: }
0776:
0777: /**
0778: * Copies/moves administrative version control information of a source file
0779: * to administrative information of a destination file.
0780: * For example, if you have manually copied/moved a source file to a target one
0781: * (manually means just in the filesystem, not using version control operations) and then
0782: * would like to turn this copying/moving into a complete version control copy
0783: * or move operation, use this method that will finish all the work for you - it
0784: * will copy/move all the necessary administrative information (kept in the source
0785: * <i>.svn</i> directory) to the target <i>.svn</i> directory.
0786: *
0787: * <p>
0788: * In that case when you have your files copied/moved in the filesystem, you
0789: * can not perform standard (version control) copying/moving - since the target already exists and
0790: * the source may be already deleted. Use this method to overcome that restriction.
0791: *
0792: * @param src a source file path (was copied/moved to <code>dst</code>)
0793: * @param dst a destination file path
0794: * @param move if <span class="javakeyword">true</span> then
0795: * completes moving <code>src</code> to <code>dst</code>,
0796: * otherwise completes copying <code>src</code> to <code>dst</code>
0797: * @throws SVNException if one of the following is true:
0798: * <ul>
0799: * <li><code>move = </code><span class="javakeyword">true</span> and <code>src</code>
0800: * still exists
0801: * <li><code>dst</code> does not exist
0802: * <li><code>dst</code> is a directory
0803: * <li><code>src</code> is a directory
0804: * <li><code>src</code> is not under version control
0805: * <li><code>dst</code> is already under version control
0806: * <li>if <code>src</code> is copied but not scheduled for
0807: * addition, and SVNKit is not able to locate the copied
0808: * directory root for <code>src</code>
0809: * </ul>
0810: */
0811: public void doVirtualCopy(File src, File dst, boolean move)
0812: throws SVNException {
0813: SVNFileType srcType = SVNFileType.getType(src);
0814: SVNFileType dstType = SVNFileType.getType(dst);
0815:
0816: String opName = move ? "move" : "copy";
0817: if (move && srcType != SVNFileType.NONE) {
0818: SVNErrorMessage err = SVNErrorMessage
0819: .create(
0820: SVNErrorCode.ENTRY_EXISTS,
0821: "Cannot perform 'virtual' {0}: ''{1}'' still exists",
0822: new Object[] { opName, src });
0823: SVNErrorManager.error(err);
0824: }
0825: if (dstType == SVNFileType.NONE) {
0826: SVNErrorMessage err = SVNErrorMessage
0827: .create(
0828: SVNErrorCode.ENTRY_NOT_FOUND,
0829: "Cannot perform 'virtual' {0}: ''{1}'' does not exist",
0830: new Object[] { opName, dst });
0831: SVNErrorManager.error(err);
0832: }
0833: if (dstType == SVNFileType.DIRECTORY) {
0834: SVNErrorMessage err = SVNErrorMessage
0835: .create(
0836: SVNErrorCode.ILLEGAL_TARGET,
0837: "Cannot perform 'virtual' {0}: ''{1}'' is a directory",
0838: new Object[] { opName, dst });
0839: SVNErrorManager.error(err);
0840: }
0841: if (!move && srcType == SVNFileType.DIRECTORY) {
0842: SVNErrorMessage err = SVNErrorMessage
0843: .create(
0844: SVNErrorCode.ILLEGAL_TARGET,
0845: "Cannot perform 'virtual' {0}: ''{1}'' is a directory",
0846: new Object[] { opName, src });
0847: SVNErrorManager.error(err);
0848: }
0849:
0850: SVNWCAccess dstAccess = createWCAccess();
0851: try {
0852: dstAccess.probeOpen(dst, false, 0);
0853: SVNEntry dstEntry = dstAccess.getEntry(dst, false);
0854: if (dstEntry != null) {
0855: SVNErrorMessage err = SVNErrorMessage
0856: .create(
0857: SVNErrorCode.ENTRY_EXISTS,
0858: "''{0}'' is already under version control",
0859: dst);
0860: SVNErrorManager.error(err);
0861: }
0862: } finally {
0863: dstAccess.close();
0864: }
0865:
0866: SVNWCAccess srcAccess = createWCAccess();
0867: String cfURL = null;
0868: boolean added = false;
0869: long cfRevision = -1;
0870: try {
0871: srcAccess.probeOpen(src, false, 0);
0872: SVNEntry srcEntry = srcAccess.getEntry(src, false);
0873: if (srcEntry == null) {
0874: SVNErrorMessage err = SVNErrorMessage.create(
0875: SVNErrorCode.ENTRY_NOT_FOUND,
0876: "''{0}'' is not under version control", src);
0877: SVNErrorManager.error(err);
0878: }
0879: if (srcEntry.isCopied()
0880: && !srcEntry.isScheduledForAddition()) {
0881: cfURL = getCopyFromURL(src.getParentFile(),
0882: SVNEncodingUtil.uriEncode(src.getName()));
0883: cfRevision = getCopyFromRevision(src.getParentFile());
0884: if (cfURL == null || cfRevision < 0) {
0885: SVNErrorMessage err = SVNErrorMessage
0886: .create(
0887: SVNErrorCode.ENTRY_NOT_FOUND,
0888: "Cannot locate copied directory root for ''{0}''",
0889: src);
0890: SVNErrorManager.error(err);
0891: }
0892: added = false;
0893: } else {
0894: cfURL = srcEntry.isCopied() ? srcEntry.getCopyFromURL()
0895: : srcEntry.getURL();
0896: cfRevision = srcEntry.isCopied() ? srcEntry
0897: .getCopyFromRevision() : srcEntry.getRevision();
0898: added = srcEntry.isScheduledForAddition()
0899: && !srcEntry.isCopied();
0900: }
0901: } finally {
0902: srcAccess.close();
0903: }
0904: if (move) {
0905: myWCClient.doDelete(src, true, false);
0906: }
0907: if (added) {
0908: myWCClient.doAdd(dst, true, false, false, false, false);
0909: return;
0910: }
0911:
0912: dstAccess = createWCAccess();
0913: srcAccess = createWCAccess();
0914: try {
0915: SVNAdminArea dstArea = dstAccess.probeOpen(dst, true, 0);
0916: SVNEntry dstEntry = dstAccess.getEntry(dst, false);
0917: if (dstEntry != null) {
0918: SVNErrorMessage err = SVNErrorMessage
0919: .create(
0920: SVNErrorCode.ENTRY_EXISTS,
0921: "''{0}'' is already under version control",
0922: dst);
0923: SVNErrorManager.error(err);
0924: }
0925:
0926: SVNAdminArea srcArea = srcAccess.probeOpen(src, false, 0);
0927: SVNVersionedProperties srcProps = srcArea.getProperties(src
0928: .getName());
0929: SVNVersionedProperties dstProps = dstArea.getProperties(dst
0930: .getName());
0931:
0932: srcProps.copyTo(dstProps);
0933:
0934: dstEntry = dstArea.addEntry(dst.getName());
0935: dstEntry.setCopyFromURL(cfURL);
0936: dstEntry.setCopyFromRevision(cfRevision);
0937: dstEntry.setCopied(true);
0938: dstEntry.setKind(SVNNodeKind.FILE);
0939: dstEntry.scheduleForAddition();
0940:
0941: dstArea.saveEntries(false);
0942: SVNLog log = dstArea.getLog();
0943: dstArea.saveVersionedProperties(log, true);
0944: log.save();
0945: dstArea.runLogs();
0946: } finally {
0947: srcAccess.close();
0948: dstAccess.close();
0949: }
0950:
0951: }
0952:
0953: private static boolean isVersionedFile(File file) {
0954: SVNWCAccess wcAccess = SVNWCAccess.newInstance(null);
0955: try {
0956: SVNAdminArea area = wcAccess.probeOpen(file, false, 0);
0957: if (area.getEntry(area.getThisDirName(), false) == null) {
0958: return false;
0959: }
0960: SVNFileType type = SVNFileType.getType(file);
0961: if (type.isFile() || type == SVNFileType.NONE) {
0962: // file or missing file
0963: return area.getEntry(file.getName(), false) != null;
0964: } else if (type != SVNFileType.NONE
0965: && !area.getRoot().equals(file)) {
0966: // directory, but not anchor. always considered unversioned.
0967: return false;
0968: }
0969: return true;
0970: } catch (SVNException e) {
0971: return false;
0972: } finally {
0973: try {
0974: wcAccess.close();
0975: } catch (SVNException svne) {
0976: //
0977: }
0978: }
0979: }
0980:
0981: private String getCopyFromURL(File path, String urlTail)
0982: throws SVNException {
0983: if (path == null) {
0984: return null;
0985: }
0986: SVNWCAccess wcAccess = createWCAccess();
0987: try {
0988: wcAccess.probeOpen(path, false, 0);
0989: } catch (SVNException e) {
0990: wcAccess.close();
0991: return null;
0992: }
0993: // urlTail is either name of an entry
0994: try {
0995: SVNEntry entry = wcAccess.getEntry(path, false);
0996: if (entry == null) {
0997: return null;
0998: }
0999: String cfURL = entry.getCopyFromURL();
1000: if (cfURL != null) {
1001: return SVNPathUtil.append(cfURL, urlTail);
1002: }
1003: urlTail = SVNPathUtil.append(SVNEncodingUtil.uriEncode(path
1004: .getName()), urlTail);
1005: path = path.getParentFile();
1006: return getCopyFromURL(path, urlTail);
1007: } finally {
1008: wcAccess.close();
1009: }
1010: }
1011:
1012: private long getCopyFromRevision(File path) throws SVNException {
1013: if (path == null) {
1014: return -1;
1015: }
1016: SVNWCAccess wcAccess = createWCAccess();
1017: try {
1018: wcAccess.probeOpen(path, false, 0);
1019: } catch (SVNException e) {
1020: wcAccess.close();
1021: return -1;
1022: }
1023: try {
1024: SVNEntry entry = wcAccess.getEntry(path, false);
1025: if (entry == null) {
1026: return -1;
1027: }
1028: long rev = entry.getCopyFromRevision();
1029: if (rev >= 0) {
1030: return rev;
1031: }
1032: path = path.getParentFile();
1033: return getCopyFromRevision(path);
1034: } finally {
1035: wcAccess.close();
1036: }
1037: }
1038: }
|