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.io.OutputStream;
0016: import java.util.ArrayList;
0017: import java.util.Arrays;
0018: import java.util.Collection;
0019: import java.util.Collections;
0020: import java.util.HashMap;
0021: import java.util.Iterator;
0022: import java.util.Map;
0023: import java.util.TreeMap;
0024:
0025: import org.tmatesoft.svn.core.SVNCommitInfo;
0026: import org.tmatesoft.svn.core.SVNErrorCode;
0027: import org.tmatesoft.svn.core.SVNErrorMessage;
0028: import org.tmatesoft.svn.core.SVNException;
0029: import org.tmatesoft.svn.core.SVNNodeKind;
0030: import org.tmatesoft.svn.core.SVNProperty;
0031: import org.tmatesoft.svn.core.SVNURL;
0032: import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
0033: import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
0034: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
0035: import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
0036: import org.tmatesoft.svn.core.internal.wc.ISVNCommitPathHandler;
0037: import org.tmatesoft.svn.core.internal.wc.SVNCancellableOutputStream;
0038: import org.tmatesoft.svn.core.internal.wc.SVNCommitMediator;
0039: import org.tmatesoft.svn.core.internal.wc.SVNCommitUtil;
0040: import org.tmatesoft.svn.core.internal.wc.SVNCommitter;
0041: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
0042: import org.tmatesoft.svn.core.internal.wc.SVNEventFactory;
0043: import org.tmatesoft.svn.core.internal.wc.SVNFileType;
0044: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
0045: import org.tmatesoft.svn.core.internal.wc.SVNPropertiesManager;
0046: import org.tmatesoft.svn.core.internal.wc.SVNWCManager;
0047: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
0048: import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
0049: import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
0050: import org.tmatesoft.svn.core.io.ISVNEditor;
0051: import org.tmatesoft.svn.core.io.SVNRepository;
0052:
0053: /**
0054: * The <b>SVNCopyClient</b> provides methods to perform any kinds of copying and moving that SVN
0055: * supports - operating on both Working Copies (WC) and URLs.
0056: *
0057: * <p>
0058: * Copy operations allow a user to copy versioned files and directories with all their
0059: * previous history in several ways.
0060: *
0061: * <p>
0062: * Supported copy operations are:
0063: * <ul>
0064: * <li> Working Copy to Working Copy (WC-to-WC) copying - this operation copies the source
0065: * Working Copy item to the destination one and schedules the source copy for addition with history.
0066: * <li> Working Copy to URL (WC-to-URL) copying - this operation commits to the repository (exactly
0067: * to that repository location that is specified by URL) a copy of the Working Copy item.
0068: * <li> URL to Working Copy (URL-to-WC) copying - this operation will copy the source item from
0069: * the repository to the Working Copy item and schedule the source copy for addition with history.
0070: * <li> URL to URL (URL-to-URL) copying - this is a fully repository-side operation, it commits
0071: * a copy of the source item to a specified repository location (within the same repository, of
0072: * course).
0073: * </ul>
0074: *
0075: * <p>
0076: * Besides just copying <b>SVNCopyClient</b> also is able to move a versioned item - that is
0077: * first making a copy of the source item and then scheduling the source item for deletion
0078: * when operating on a Working Copy, or right committing the deletion of the source item when
0079: * operating immediately on the repository.
0080: *
0081: * <p>
0082: * Supported move operations are:
0083: * <ul>
0084: * <li> Working Copy to Working Copy (WC-to-WC) moving - this operation copies the source
0085: * Working Copy item to the destination one and schedules the source item for deletion.
0086: * <li> URL to URL (URL-to-URL) moving - this is a fully repository-side operation, it commits
0087: * a copy of the source item to a specified repository location and deletes the source item.
0088: * </ul>
0089: *
0090: * <p>
0091: * Overloaded <b>doCopy()</b> methods of <b>SVNCopyClient</b> are similar to
0092: * <code>'svn copy'</code> and <code>'svn move'</code> commands of the SVN command line client.
0093: *
0094: * @version 1.1.1
0095: * @author TMate Software Ltd.
0096: * @see <a target="_top" href="http://svnkit.com/kb/examples/">Examples</a>
0097: *
0098: */
0099: public class SVNCopyClient extends SVNBasicClient {
0100:
0101: private ISVNCommitHandler myCommitHandler;
0102: private ISVNCommitParameters myCommitParameters;
0103:
0104: /**
0105: * Constructs and initializes an <b>SVNCopyClient</b> object
0106: * with the specified run-time configuration and authentication
0107: * drivers.
0108: *
0109: * <p>
0110: * If <code>options</code> is <span class="javakeyword">null</span>,
0111: * then this <b>SVNCopyClient</b> will be using a default run-time
0112: * configuration driver which takes client-side settings from the
0113: * default SVN's run-time configuration area but is not able to
0114: * change those settings (read more on {@link ISVNOptions} and {@link SVNWCUtil}).
0115: *
0116: * <p>
0117: * If <code>authManager</code> is <span class="javakeyword">null</span>,
0118: * then this <b>SVNCopyClient</b> will be using a default authentication
0119: * and network layers driver (see {@link SVNWCUtil#createDefaultAuthenticationManager()})
0120: * which uses server-side settings and auth storage from the
0121: * default SVN's run-time configuration area (or system properties
0122: * if that area is not found).
0123: *
0124: * @param authManager an authentication and network layers driver
0125: * @param options a run-time configuration options driver
0126: */
0127: public SVNCopyClient(ISVNAuthenticationManager authManager,
0128: ISVNOptions options) {
0129: super (authManager, options);
0130: }
0131:
0132: public SVNCopyClient(ISVNRepositoryPool repositoryPool,
0133: ISVNOptions options) {
0134: super (repositoryPool, options);
0135: }
0136:
0137: /**
0138: * Sets an implementation of <b>ISVNCommitHandler</b> to
0139: * the commit handler that will be used during commit operations to handle
0140: * commit log messages. The handler will receive a clien's log message and items
0141: * (represented as <b>SVNCommitItem</b> objects) that will be
0142: * committed. Depending on implementor's aims the initial log message can
0143: * be modified (or something else) and returned back.
0144: *
0145: * <p>
0146: * If using <b>SVNCopyClient</b> without specifying any
0147: * commit handler then a default one will be used - {@link DefaultSVNCommitHandler}.
0148: *
0149: * @param handler an implementor's handler that will be used to handle
0150: * commit log messages
0151: * @see #getCommitHandler()
0152: * @see SVNCommitItem
0153: */
0154: public void setCommitHandler(ISVNCommitHandler handler) {
0155: myCommitHandler = handler;
0156: }
0157:
0158: /**
0159: * Returns the specified commit handler (if set) being in use or a default one
0160: * (<b>DefaultSVNCommitHandler</b>) if no special
0161: * implementations of <b>ISVNCommitHandler</b> were
0162: * previousely provided.
0163: *
0164: * @return the commit handler being in use or a default one
0165: * @see #setCommitHandler(ISVNCommitHandler)
0166: * @see DefaultSVNCommitHandler
0167: */
0168: public ISVNCommitHandler getCommitHandler() {
0169: if (myCommitHandler == null) {
0170: myCommitHandler = new DefaultSVNCommitHandler();
0171: }
0172: return myCommitHandler;
0173: }
0174:
0175: /**
0176: * Sets commit parameters to use.
0177: *
0178: * <p>
0179: * When no parameters are set {@link DefaultSVNCommitParameters default}
0180: * ones are used.
0181: *
0182: * @param parameters commit parameters
0183: * @see #getCommitParameters()
0184: */
0185: public void setCommitParameters(ISVNCommitParameters parameters) {
0186: myCommitParameters = parameters;
0187: }
0188:
0189: /**
0190: * Returns commit parameters.
0191: *
0192: * <p>
0193: * If no user parameters were previously specified, once creates and
0194: * returns {@link DefaultSVNCommitParameters default} ones.
0195: *
0196: * @return commit parameters
0197: * @see #setCommitParameters(ISVNCommitParameters)
0198: */
0199: public ISVNCommitParameters getCommitParameters() {
0200: if (myCommitParameters == null) {
0201: myCommitParameters = new DefaultSVNCommitParameters();
0202: }
0203: return myCommitParameters;
0204: }
0205:
0206: /**
0207: * Copies/moves a source URL to a destination one immediately committing changes
0208: * to a repository. Equivalent to <code>doCopy(srcURL, srcRevision, dstURL, isMove, false, commitMessage)</code>.
0209: *
0210: * @param srcURL a source repository location URL
0211: * @param srcRevision a revision of <code>srcURL</code>
0212: * @param dstURL a target URL where <code>srcURL</code> is to be
0213: * copied/moved
0214: * @param isMove <span class="javakeyword">true</span> to move the source
0215: * to the target (only URL-to-URL),
0216: * <span class="javakeyword">false</span> to copy
0217: * @param commitMessage a commit log message
0218: * @return information on the committed revision
0219: * @throws SVNException
0220: * @see #doCopy(SVNURL, SVNRevision, SVNURL, boolean, boolean, String)
0221: */
0222: public SVNCommitInfo doCopy(SVNURL srcURL, SVNRevision srcRevision,
0223: SVNURL dstURL, boolean isMove, String commitMessage)
0224: throws SVNException {
0225: return doCopy(srcURL, srcRevision, dstURL, isMove, false,
0226: commitMessage);
0227: }
0228:
0229: /**
0230: * Copies/moves a source URL to a destination one immediately committing changes
0231: * to a repository.
0232: *
0233: * <p>
0234: * If <code>dstURL</code> and <code>srcURL</code> are the same,
0235: * <code>failWhenDstExists</code> is <span class="javakeyword">false</span> and
0236: * <code>srcURL</code> is a directory then this directory will be copied into itself.
0237: *
0238: * <p>
0239: * If <code>dstURL</code> is a directory, <code>dstURL</code> and <code>srcURL</code> are not the same,
0240: * <code>failWhenDstExists</code> is <span class="javakeyword">false</span>, <code>dstURL</code>
0241: * has not the last path element entry of <code>srcURL</code> then that entry will be copied into
0242: * <code>dstURL</code>.
0243: *
0244: * @param srcURL a source repository location URL
0245: * @param srcRevision a revision of <code>srcURL</code>
0246: * @param dstURL a target URL where <code>srcURL</code> is to be
0247: * copied/moved
0248: * @param isMove <span class="javakeyword">true</span> to move the source
0249: * to the target (only URL-to-URL),
0250: * <span class="javakeyword">false</span> to copy
0251: * @param failWhenDstExists <span class="javakeyword">true</span> to force a failure if
0252: * the destination exists
0253: * @param commitMessage a commit log message
0254: * @return information on the committed revision
0255: * @throws SVNException if one of the following is true:
0256: * <ul>
0257: * <li><code>srcURL</code> and <code>dstURL</code> are not in the
0258: * same repository
0259: * <li><code>srcURL</code> was not found in <code>srcRevision</code>
0260: * <li><code>dstURL</code> and <code>srcURL</code> are the same and
0261: * <code>failWhenDstExists</code> is <span class="javakeyword">true</span>
0262: * <li><code>dstURL</code> already exists and <code>failWhenDstExists</code>
0263: * is <span class="javakeyword">true</span>
0264: * <li><code>dstURL</code> already exists, <code>failWhenDstExists</code>
0265: * is <span class="javakeyword">false</span>, but <code>dstURL</code>
0266: * already contains the top path element name of <code>srcURL</code>
0267: * <li><code>isMove = </code><span class="javakeyword">true</span> and
0268: * <code>dstURL = srcURL</code>
0269: * </ul>
0270: */
0271: public SVNCommitInfo doCopy(SVNURL srcURL, SVNRevision srcRevision,
0272: SVNURL dstURL, boolean isMove, boolean failWhenDstExists,
0273: String commitMessage) throws SVNException {
0274: SVNURL topURL = SVNURLUtil.getCommonURLAncestor(srcURL, dstURL);
0275: if (topURL == null) {
0276: SVNErrorMessage err = SVNErrorMessage
0277: .create(
0278: SVNErrorCode.UNSUPPORTED_FEATURE,
0279: "Source and dest appear not to be in the same repository (src: ''{0}''; dst: ''{1}'')",
0280: new Object[] { srcURL, dstURL });
0281: SVNErrorManager.error(err);
0282: }
0283: boolean isResurrect = false;
0284:
0285: if (dstURL.equals(srcURL)) {
0286: topURL = srcURL.removePathTail();
0287: isResurrect = true;
0288: }
0289:
0290: SVNRepository repository = createRepository(topURL, true);
0291: if (!dstURL.equals(repository.getRepositoryRoot(true))
0292: && srcURL.getPath().startsWith(dstURL.getPath() + "/")) {
0293: isResurrect = true;
0294: topURL = topURL.removePathTail();
0295: repository = createRepository(topURL, true);
0296: }
0297:
0298: // substring one more char, because path always starts with /, and we need relative path.
0299: String srcPath = srcURL.equals(topURL) ? "" : srcURL
0300: .getURIEncodedPath().substring(
0301: topURL.getURIEncodedPath().length() + 1);
0302: srcPath = SVNEncodingUtil.uriDecode(srcPath);
0303: String dstPath = dstURL.equals(topURL) ? "" : dstURL
0304: .getURIEncodedPath().substring(
0305: topURL.getURIEncodedPath().length() + 1);
0306: dstPath = SVNEncodingUtil.uriDecode(dstPath);
0307:
0308: if ("".equals(srcPath) && isMove) {
0309: SVNErrorMessage err = SVNErrorMessage.create(
0310: SVNErrorCode.UNSUPPORTED_FEATURE,
0311: "Cannot move URL ''{0}'' into itself", srcURL);
0312: SVNErrorManager.error(err);
0313: }
0314:
0315: long srcRevNumber = getRevisionNumber(srcRevision, repository,
0316: null);
0317: long latestRevision = repository.getLatestRevision();
0318:
0319: if (srcRevNumber < 0) {
0320: srcRevNumber = latestRevision;
0321: }
0322: SVNNodeKind srcKind = repository.checkPath(srcPath,
0323: srcRevNumber);
0324: if (srcKind == SVNNodeKind.NONE) {
0325: SVNErrorMessage err = SVNErrorMessage.create(
0326: SVNErrorCode.FS_NOT_FOUND,
0327: "Path ''{0}'' does not exist in revision {1}",
0328: new Object[] { srcURL, new Long(srcRevNumber) });
0329: SVNErrorManager.error(err);
0330: }
0331: SVNNodeKind dstKind = repository.checkPath(dstPath,
0332: latestRevision);
0333: if (dstKind == SVNNodeKind.DIR) {
0334: if (failWhenDstExists) {
0335: SVNErrorMessage err = SVNErrorMessage.create(
0336: SVNErrorCode.FS_ALREADY_EXISTS,
0337: "Path ''{0}'' already exists", dstPath);
0338: SVNErrorManager.error(err);
0339: }
0340: dstPath = SVNPathUtil.append(dstPath, SVNPathUtil
0341: .tail(srcURL.getPath()));
0342: if (repository.checkPath(dstPath, latestRevision) != SVNNodeKind.NONE) {
0343: SVNErrorMessage err = SVNErrorMessage.create(
0344: SVNErrorCode.FS_ALREADY_EXISTS,
0345: "Path ''{0}'' already exists", dstPath);
0346: SVNErrorManager.error(err);
0347: }
0348: } else if (dstKind == SVNNodeKind.FILE) {
0349: SVNErrorMessage err = SVNErrorMessage.create(
0350: SVNErrorCode.FS_ALREADY_EXISTS,
0351: "Path ''{0}'' already exists", dstPath);
0352: SVNErrorManager.error(err);
0353: }
0354: Collection commitItems = new ArrayList(2);
0355: commitItems.add(new SVNCommitItem(null, dstURL, srcURL,
0356: srcKind,
0357: SVNRevision.UNDEFINED/*create(srcRevNumber)*/,
0358: SVNRevision.create(srcRevNumber), true, false, false,
0359: false, true, false));
0360: if (isMove) {
0361: commitItems.add(new SVNCommitItem(null, srcURL, null,
0362: srcKind, SVNRevision.create(srcRevNumber),
0363: SVNRevision.UNDEFINED, false, true, false, false,
0364: false, false));
0365: }
0366: commitMessage = getCommitHandler()
0367: .getCommitMessage(
0368: commitMessage,
0369: (SVNCommitItem[]) commitItems
0370: .toArray(new SVNCommitItem[commitItems
0371: .size()]));
0372: if (commitMessage == null) {
0373: return SVNCommitInfo.NULL;
0374: }
0375:
0376: commitMessage = SVNCommitClient
0377: .validateCommitMessage(commitMessage);
0378: ISVNEditor commitEditor = repository.getCommitEditor(
0379: commitMessage, null, false, null);
0380: ISVNCommitPathHandler committer = new CopyCommitPathHandler(
0381: srcPath, srcRevNumber, srcKind, dstPath, isMove,
0382: isResurrect);
0383: Collection paths = isMove ? Arrays.asList(new String[] {
0384: srcPath, dstPath }) : Collections
0385: .singletonList(dstPath);
0386:
0387: SVNCommitInfo result = null;
0388: try {
0389: SVNCommitUtil.driveCommitEditor(committer, paths,
0390: commitEditor, -1);
0391: result = commitEditor.closeEdit();
0392: } catch (SVNException e) {
0393: try {
0394: commitEditor.abortEdit();
0395: } catch (SVNException inner) {
0396: //
0397: }
0398: SVNErrorMessage nestedErr = e.getErrorMessage();
0399: SVNErrorMessage err = SVNErrorMessage.create(nestedErr
0400: .getErrorCode(), "Commit failed (details follow):");
0401: SVNErrorManager.error(err, e.getErrorMessage());
0402: }
0403: if (result != null && result.getNewRevision() >= 0) {
0404: dispatchEvent(SVNEventFactory.createCommitCompletedEvent(
0405: null, result.getNewRevision()),
0406: ISVNEventHandler.UNKNOWN);
0407: }
0408: return result != null ? result : SVNCommitInfo.NULL;
0409: }
0410:
0411: /**
0412: * Copies a source Working Copy path (or its repository location URL) to a destination
0413: * URL immediately committing changes to a repository.
0414: *
0415: * <p>
0416: * Equivalent to <code>doCopy(srcPath, srcRevision, dstURL, false, commitMessage)</code>.
0417: *
0418: * @param srcPath a source Working Copy path
0419: * @param srcRevision a revision of <code>srcPath</code>
0420: * @param dstURL a target URL where <code>srcPath</code> is to be
0421: * copied
0422: * @param commitMessage a commit log message
0423: * @return information on the committed revision
0424: * @throws SVNException if one of the following is true:
0425: * <ul>
0426: * <li><code>srcPath</code> is not under version control
0427: * <li><code>srcPath</code> has no URL
0428: * <li>the repository location of <code>srcPath</code> was not
0429: * found in <code>srcRevision</code>
0430: * <li><code>dstURL</code> already exists
0431: * </ul>
0432: * @see #doCopy(File, SVNRevision, SVNURL, boolean, String)
0433: */
0434: public SVNCommitInfo doCopy(File srcPath, SVNRevision srcRevision,
0435: SVNURL dstURL, String commitMessage) throws SVNException {
0436: return doCopy(srcPath, srcRevision, dstURL, false,
0437: commitMessage);
0438: }
0439:
0440: /**
0441: * Copies a source Working Copy path (or its repository location URL) to a destination
0442: * URL immediately committing changes to a repository.
0443: *
0444: * <p>
0445: * If <code>srcRevision</code> is not {@link SVNRevision#WORKING} then the repository
0446: * location URL of <code>srcPath</code> is copied to <code>dstURL</code>. Otherwise
0447: * <code>srcPath</code> itself.
0448: *
0449: * <p>
0450: * <code>failWhenDstExists</code> behaves
0451: * like in {@link #doCopy(SVNURL, SVNRevision, SVNURL, boolean, boolean, String)}.
0452: *
0453: * @param srcPath a source Working Copy path
0454: * @param srcRevision a revision of <code>srcPath</code>
0455: * @param dstURL a target URL where <code>srcPath</code> is to be
0456: * copied
0457: * @param failWhenDstExists <span class="javakeyword">true</span> to force a failure if
0458: * the destination exists
0459: * @param commitMessage a commit log message
0460: * @return information on the committed revision
0461: * @throws SVNException if one of the following is true:
0462: * <ul>
0463: * <li><code>srcPath</code> is not under version control
0464: * <li><code>srcPath</code> has no URL
0465: * <li>the repository location of <code>srcPath</code> was not
0466: * found in <code>srcRevision</code>
0467: * <li><code>dstURL</code> already exists and
0468: * <code>failWhenDstExists</code> is <span class="javakeyword">true</span>
0469: * </ul>
0470: */
0471: public SVNCommitInfo doCopy(File srcPath, SVNRevision srcRevision,
0472: SVNURL dstURL, boolean failWhenDstExists,
0473: String commitMessage) throws SVNException {
0474: // may be url->url.
0475: srcPath = new File(SVNPathUtil.validateFilePath(srcPath
0476: .getAbsolutePath()));
0477: if (srcRevision.isValid() && srcRevision != SVNRevision.WORKING) {
0478: SVNWCAccess wcAccess = createWCAccess();
0479: wcAccess.probeOpen(srcPath, false, 0);
0480: SVNEntry srcEntry = wcAccess.getEntry(srcPath, false);
0481: wcAccess.close();
0482:
0483: if (srcEntry == null) {
0484: SVNErrorMessage err = SVNErrorMessage
0485: .create(SVNErrorCode.UNVERSIONED_RESOURCE,
0486: "''{0}'' is not under version control",
0487: srcPath);
0488: SVNErrorManager.error(err);
0489: }
0490: if (srcEntry.getURL() == null) {
0491: SVNErrorMessage err = SVNErrorMessage
0492: .create(
0493: SVNErrorCode.ENTRY_MISSING_URL,
0494: "''{0}'' does not seem to have a URL associated with it",
0495: srcPath);
0496: SVNErrorManager.error(err);
0497: }
0498: return doCopy(srcEntry.getSVNURL(), srcRevision, dstURL,
0499: false, failWhenDstExists, commitMessage);
0500: }
0501: SVNWCAccess wcAccess = createWCAccess();
0502: SVNAdminArea adminArea = wcAccess.probeOpen(srcPath, false,
0503: SVNWCAccess.INFINITE_DEPTH);
0504: wcAccess.setAnchor(adminArea.getRoot());
0505:
0506: SVNURL dstAnchorURL = dstURL.removePathTail();
0507: String dstTarget = SVNPathUtil.tail(dstURL.toString());
0508: dstTarget = SVNEncodingUtil.uriDecode(dstTarget);
0509:
0510: SVNRepository repository = createRepository(dstAnchorURL, true);
0511: SVNNodeKind dstKind = repository.checkPath(dstTarget, -1);
0512: if (dstKind == SVNNodeKind.DIR) {
0513: if (failWhenDstExists) {
0514: SVNErrorMessage err = SVNErrorMessage.create(
0515: SVNErrorCode.FS_ALREADY_EXISTS,
0516: "Path ''{0}'' already exists", dstURL);
0517: SVNErrorManager.error(err);
0518: }
0519: dstURL = dstURL.appendPath(srcPath.getName(), false);
0520: } else if (dstKind == SVNNodeKind.FILE) {
0521: SVNErrorMessage err = SVNErrorMessage.create(
0522: SVNErrorCode.FS_ALREADY_EXISTS,
0523: "File ''{0}'' already exists", dstURL);
0524: SVNErrorManager.error(err);
0525: }
0526:
0527: SVNCommitItem[] items = new SVNCommitItem[] { new SVNCommitItem(
0528: null, dstURL, null, SVNNodeKind.NONE,
0529: SVNRevision.UNDEFINED, SVNRevision.UNDEFINED, true,
0530: false, false, false, true, false) };
0531: items[0].setWCAccess(adminArea.getWCAccess());
0532: commitMessage = getCommitHandler().getCommitMessage(
0533: commitMessage, items);
0534: if (commitMessage == null) {
0535: return SVNCommitInfo.NULL;
0536: }
0537:
0538: SVNAdminArea dirArea = null;
0539: if (SVNFileType.getType(srcPath) == SVNFileType.DIRECTORY) {
0540: dirArea = wcAccess.retrieve(srcPath);
0541: } else {
0542: dirArea = adminArea;
0543: }
0544:
0545: Collection tmpFiles = null;
0546: SVNCommitInfo info = null;
0547: ISVNEditor commitEditor = null;
0548: try {
0549: Map commitables = new TreeMap();
0550: SVNEntry entry = wcAccess.getEntry(srcPath, false);
0551: if (entry == null) {
0552: SVNErrorMessage err = SVNErrorMessage
0553: .create(SVNErrorCode.ENTRY_NOT_FOUND,
0554: "''{0}'' is not under version control",
0555: srcPath);
0556: SVNErrorManager.error(err);
0557: return SVNCommitInfo.NULL;
0558: }
0559:
0560: SVNCommitUtil.harvestCommitables(commitables, dirArea,
0561: srcPath, null, entry, dstURL.toString(), entry
0562: .getURL(), true, false, false, null, true,
0563: false, getCommitParameters());
0564: items = (SVNCommitItem[]) commitables.values().toArray(
0565: new SVNCommitItem[commitables.values().size()]);
0566: for (int i = 0; i < items.length; i++) {
0567: items[i].setWCAccess(wcAccess);
0568: }
0569:
0570: commitables = new TreeMap();
0571: dstURL = SVNURL.parseURIEncoded(SVNCommitUtil
0572: .translateCommitables(items, commitables));
0573:
0574: repository = createRepository(dstURL, true);
0575: SVNCommitMediator mediator = new SVNCommitMediator(
0576: commitables);
0577: tmpFiles = mediator.getTmpFiles();
0578:
0579: commitMessage = SVNCommitClient
0580: .validateCommitMessage(commitMessage);
0581: SVNURL root = repository.getRepositoryRoot(true);
0582: commitEditor = repository.getCommitEditor(commitMessage,
0583: null, false, mediator);
0584: info = SVNCommitter.commit(tmpFiles, commitables, root
0585: .getPath(), commitEditor);
0586: commitEditor = null;
0587: } finally {
0588: if (tmpFiles != null) {
0589: for (Iterator files = tmpFiles.iterator(); files
0590: .hasNext();) {
0591: File file = (File) files.next();
0592: file.delete();
0593: }
0594: }
0595: if (commitEditor != null && info == null) {
0596: commitEditor.abortEdit();
0597: }
0598: if (wcAccess != null) {
0599: wcAccess.close();
0600: }
0601: }
0602: if (info != null && info.getNewRevision() >= 0) {
0603: dispatchEvent(SVNEventFactory.createCommitCompletedEvent(
0604: null, info.getNewRevision()),
0605: ISVNEventHandler.UNKNOWN);
0606: }
0607: return info != null ? info : SVNCommitInfo.NULL;
0608: }
0609:
0610: /**
0611: * Copies a source URL to a destination Working Copy path.
0612: *
0613: * <p>
0614: * <code>dstPath</code> will be automatically scheduled for addition with history.
0615: *
0616: * @param srcURL a source URL
0617: * @param srcRevision a revision of <code>srcURL</code>
0618: * @param dstPath a destination WC path
0619: * @return the revision number of a source
0620: * @throws SVNException if one of the following is true:
0621: * <ul>
0622: * <li><code>srcURL</code> was not found in <code>srcRevision</code>
0623: * <li><code>dstPath</code> already exists
0624: * <li><code>dstPath</code> appears in <code>srcURL</code>
0625: * <li><code>dstPath</code> and <code>srcURL</code> are from
0626: * different repositories
0627: * <li><code>dstPath</code> is under version control but missing
0628: * </ul>
0629: */
0630: public long doCopy(SVNURL srcURL, SVNRevision srcRevision,
0631: File dstPath) throws SVNException {
0632: SVNRepository repository = createRepository(srcURL, true);
0633: if (!srcRevision.isValid()) {
0634: srcRevision = SVNRevision.HEAD;
0635: }
0636: long srcRevisionNumber = getRevisionNumber(srcRevision,
0637: repository, null);
0638: SVNNodeKind srcKind = repository.checkPath("",
0639: srcRevisionNumber);
0640:
0641: if (srcKind == SVNNodeKind.NONE) {
0642: if (SVNRevision.isValidRevisionNumber(srcRevisionNumber)) {
0643: SVNErrorMessage err = SVNErrorMessage.create(
0644: SVNErrorCode.FS_NOT_FOUND,
0645: "Path ''{0}'' not found in revision {1}",
0646: new Object[] { srcURL,
0647: new Long(srcRevisionNumber) });
0648: SVNErrorManager.error(err);
0649: } else {
0650: SVNErrorMessage err = SVNErrorMessage.create(
0651: SVNErrorCode.FS_NOT_FOUND,
0652: "Path ''{0}'' not found in head revision",
0653: srcURL);
0654: SVNErrorManager.error(err);
0655: }
0656: }
0657:
0658: SVNFileType dstFileType = SVNFileType.getType(dstPath);
0659: if (dstFileType == SVNFileType.DIRECTORY) {
0660: dstPath = new File(dstPath, SVNPathUtil.tail(srcURL
0661: .getPath()));
0662: } else if (dstFileType != SVNFileType.NONE) {
0663: SVNErrorMessage err = SVNErrorMessage.create(
0664: SVNErrorCode.ENTRY_EXISTS,
0665: "File ''{0}'' already exists", dstPath);
0666: SVNErrorManager.error(err);
0667: }
0668: dstFileType = SVNFileType.getType(dstPath);
0669: if (dstFileType != SVNFileType.NONE) {
0670: SVNErrorMessage err = SVNErrorMessage.create(
0671: SVNErrorCode.WC_OBSTRUCTED_UPDATE,
0672: "''{0}'' is in the way", dstPath);
0673: SVNErrorManager.error(err);
0674: }
0675:
0676: SVNWCAccess dstAccess = createWCAccess();
0677: long revision = -1;
0678: try {
0679: SVNAdminArea adminArea = dstAccess.probeOpen(dstPath, true,
0680: 0);
0681:
0682: SVNEntry dstEntry = dstAccess.getEntry(dstPath, false);
0683: if (dstEntry != null && !dstEntry.isDirectory()
0684: && !dstEntry.isScheduledForDeletion()) {
0685: SVNErrorMessage err = SVNErrorMessage
0686: .create(
0687: SVNErrorCode.WC_OBSTRUCTED_UPDATE,
0688: "Entry for ''{0}'' exists (though the working copy file is missing)",
0689: dstPath);
0690: SVNErrorManager.error(err);
0691: }
0692:
0693: boolean sameRepositories;
0694:
0695: String srcUUID = null;
0696: String dstUUID = null;
0697:
0698: try {
0699: srcUUID = repository.getRepositoryUUID(true);
0700: dstUUID = getUUIDFromPath(dstAccess, dstPath
0701: .getParentFile());
0702: } catch (SVNException e) {
0703: if (e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NO_REPOS_UUID) {
0704: srcUUID = dstUUID = null;
0705: } else {
0706: throw e;
0707: }
0708: }
0709: if (dstUUID == null || srcUUID == null) {
0710: sameRepositories = false;
0711: } else {
0712: sameRepositories = srcUUID.equals(dstUUID);
0713: }
0714:
0715: if (srcKind == SVNNodeKind.DIR) {
0716: // do checkout.
0717: SVNUpdateClient updateClient = new SVNUpdateClient(
0718: getRepositoryPool(), getOptions());
0719: updateClient.setEventHandler(getEventDispatcher());
0720:
0721: revision = updateClient.doCheckout(srcURL, dstPath,
0722: srcRevision, srcRevision, true);
0723:
0724: if (srcRevision == SVNRevision.HEAD && sameRepositories) {
0725: SVNAdminArea dstArea = dstAccess.open(dstPath,
0726: true, SVNWCAccess.INFINITE_DEPTH);
0727: SVNEntry dstRootEntry = dstArea.getEntry(dstArea
0728: .getThisDirName(), false);
0729: revision = dstRootEntry.getRevision();
0730: }
0731: if (sameRepositories) {
0732: SVNWCManager.add(dstPath, adminArea, srcURL,
0733: revision);
0734: } else {
0735: SVNErrorMessage err = SVNErrorMessage
0736: .create(
0737: SVNErrorCode.UNSUPPORTED_FEATURE,
0738: "Source URL ''{0}'' is from foreign repository; leaving it as a disjoint WC",
0739: srcURL);
0740: SVNErrorManager.error(err);
0741: }
0742: } else if (srcKind == SVNNodeKind.FILE) {
0743: Map properties = new HashMap();
0744: File tmpFile = null;
0745:
0746: File baseTmpFile = adminArea.getBaseFile(dstPath
0747: .getName(), true);
0748: tmpFile = SVNFileUtil.createUniqueFile(baseTmpFile
0749: .getParentFile(), ".copy", ".tmp");
0750: OutputStream os = null;
0751:
0752: long realRevision = -1;
0753: try {
0754: os = SVNFileUtil.openFileForWriting(tmpFile);
0755: realRevision = repository.getFile("",
0756: srcRevisionNumber, properties,
0757: new SVNCancellableOutputStream(os, this ));
0758: } finally {
0759: SVNFileUtil.closeFile(os);
0760: }
0761: if (!SVNRevision
0762: .isValidRevisionNumber(srcRevisionNumber)) {
0763: srcRevisionNumber = realRevision;
0764: }
0765:
0766: SVNWCManager.addRepositoryFile(adminArea, dstPath
0767: .getName(), null, tmpFile, null, properties,
0768: sameRepositories ? srcURL.toString() : null,
0769: sameRepositories ? srcRevisionNumber : -1);
0770:
0771: dispatchEvent(SVNEventFactory.createAddedEvent(null,
0772: adminArea, dstAccess.getEntry(dstPath, false)));
0773: revision = srcRevisionNumber;
0774: sleepForTimeStamp();
0775: }
0776: } finally {
0777: dstAccess.close();
0778: }
0779: return revision;
0780: }
0781:
0782: private String getUUIDFromPath(SVNWCAccess wcAccess, File path)
0783: throws SVNException {
0784: SVNEntry entry = wcAccess.getEntry(path, true);
0785: if (entry == null) {
0786: SVNErrorMessage err = SVNErrorMessage.create(
0787: SVNErrorCode.ENTRY_NOT_FOUND,
0788: "Can''t find entry for ''{0}''", path);
0789: SVNErrorManager.error(err);
0790: }
0791:
0792: String uuid = null;
0793: if (entry.getUUID() != null) {
0794: uuid = entry.getUUID();
0795: } else if (entry.getURL() != null) {
0796: SVNRepository repos = createRepository(entry.getSVNURL(),
0797: false);
0798: try {
0799: uuid = repos.getRepositoryUUID(true);
0800: } finally {
0801: repos.closeSession();
0802: }
0803: } else {
0804: if (wcAccess.isWCRoot(path)) {
0805: SVNErrorMessage err = SVNErrorMessage.create(
0806: SVNErrorCode.ENTRY_MISSING_URL,
0807: "''{0}'' has no URL", path);
0808: SVNErrorManager.error(err);
0809: }
0810: uuid = getUUIDFromPath(wcAccess, path.getParentFile());
0811: }
0812: return uuid;
0813: }
0814:
0815: /**
0816: * Copies/moves a source Working Copy path to a destination Working Copy path.
0817: *
0818: * <p>
0819: * If <code>srcRevision</code> is not {@link SVNRevision#WORKING} and
0820: * <code>isMove = </code><span class="javakeyword">false</span>, then the repository
0821: * location URL of <code>srcPath</code> is copied to <code>dstPath</code>. Otherwise
0822: * <code>srcPath</code> itself.
0823: *
0824: * <p>
0825: * <code>dstPath</code> will be automatically scheduled for addition with history.
0826: *
0827: * @param srcPath a source WC path
0828: * @param srcRevision a revision of <code>srcPath</code>
0829: * @param dstPath a destination WC path
0830: * @param force <span class="javakeyword">true</span> to force the operation
0831: * to run
0832: * @param isMove <span class="javakeyword">true</span> to move the source
0833: * to the target (only WC-to-WC),
0834: * <span class="javakeyword">false</span> to copy
0835: * @throws SVNException if one of the following is true:
0836: * <ul>
0837: * <li><code>dstPath</code> already exists and is in the way
0838: * containing an item with the same name as the source
0839: * <li><code>srcPath</code> is not under version control
0840: * <li><code>srcPath</code> does not exist
0841: * <li><code>srcPath</code> has no URL
0842: * <li><code>dstPath</code> is a child of <code>srcPath</code>
0843: * <li><code>dstPath</code> is scheduled for deletion
0844: * <li><code>isMove = </code><span class="javakeyword">true</span> and
0845: * <code>dstURL = srcURL</code>
0846: * </ul>
0847: */
0848: public void doCopy(File srcPath, SVNRevision srcRevision,
0849: File dstPath, boolean force, boolean isMove)
0850: throws SVNException {
0851: srcPath = new File(SVNPathUtil.validateFilePath(srcPath
0852: .getAbsolutePath())).getAbsoluteFile();
0853: dstPath = new File(SVNPathUtil.validateFilePath(dstPath
0854: .getAbsolutePath())).getAbsoluteFile();
0855: if (srcRevision.isValid() && srcRevision != SVNRevision.WORKING
0856: && !isMove) {
0857: // url->wc copy
0858: SVNWCAccess wcAccess = createWCAccess();
0859: SVNURL srcURL = null;
0860: try {
0861: wcAccess.probeOpen(srcPath, false, 0);
0862: SVNEntry srcEntry = wcAccess.getEntry(srcPath, false);
0863: if (srcEntry == null) {
0864: SVNErrorMessage err = SVNErrorMessage.create(
0865: SVNErrorCode.ENTRY_NOT_FOUND,
0866: "''{0}'' is not under version control",
0867: srcPath);
0868: SVNErrorManager.error(err);
0869: }
0870: if (srcEntry.getURL() == null) {
0871: SVNErrorMessage err = SVNErrorMessage.create(
0872: SVNErrorCode.ENTRY_MISSING_URL,
0873: "''{0}'' has no URL", srcPath);
0874: SVNErrorManager.error(err);
0875: }
0876: srcURL = srcEntry.getSVNURL();
0877: } finally {
0878: wcAccess.close();
0879: }
0880: doCopy(srcURL, srcRevision, dstPath);
0881: return;
0882: }
0883: // 1. can't copy src to its own child
0884: if (SVNPathUtil.isChildOf(srcPath, dstPath)
0885: || srcPath.equals(dstPath)) {
0886: SVNErrorMessage err = SVNErrorMessage.create(
0887: SVNErrorCode.UNSUPPORTED_FEATURE,
0888: "Cannot copy ''{0}'' into its own child ''{1}''",
0889: new Object[] { srcPath, dstPath });
0890: SVNErrorManager.error(err);
0891: }
0892: // 2. can't move path into itself
0893: if (isMove && srcPath.equals(dstPath)) {
0894: SVNErrorMessage err = SVNErrorMessage.create(
0895: SVNErrorCode.UNSUPPORTED_FEATURE,
0896: "Cannot move ''{0}'' into itself", srcPath);
0897: SVNErrorManager.error(err);
0898: }
0899: // 3. src should exist
0900: SVNFileType srcType = SVNFileType.getType(srcPath);
0901: if (srcType == SVNFileType.NONE) {
0902: SVNErrorMessage err = SVNErrorMessage.create(
0903: SVNErrorCode.NODE_UNKNOWN_KIND,
0904: "Path ''{0}'' does not exist", srcPath);
0905: SVNErrorManager.error(err);
0906: }
0907: // 4. if dst exists - use its child
0908: SVNFileType dstType = SVNFileType.getType(dstPath);
0909: if (dstType == SVNFileType.DIRECTORY) {
0910: dstPath = new File(dstPath, srcPath.getName());
0911: dstType = SVNFileType.getType(dstPath);
0912: if (dstType != SVNFileType.NONE) {
0913: SVNErrorMessage err = SVNErrorMessage.create(
0914: SVNErrorCode.WC_OBSTRUCTED_UPDATE,
0915: "''{0}'' already exists and is in the way",
0916: dstPath);
0917: SVNErrorManager.error(err);
0918: }
0919: } else if (dstType != SVNFileType.NONE) {
0920: SVNErrorMessage err = SVNErrorMessage.create(
0921: SVNErrorCode.ENTRY_EXISTS,
0922: "File ''{0}'' already exists", dstPath);
0923: SVNErrorManager.error(err);
0924: }
0925:
0926: SVNWCAccess wcAccess = createWCAccess();
0927: SVNAdminArea adminArea = null;
0928: File srcParent = srcPath.getParentFile();
0929: File dstParent = dstPath.getParentFile();
0930:
0931: try {
0932: SVNAdminArea srcParentArea = null;
0933: if (isMove) {
0934: srcParentArea = wcAccess
0935: .open(
0936: srcParent,
0937: true,
0938: srcType == SVNFileType.DIRECTORY ? SVNWCAccess.INFINITE_DEPTH
0939: : 0);
0940: if (srcParent.equals(dstParent)) {
0941: adminArea = srcParentArea;
0942: } else {
0943: if (srcType == SVNFileType.DIRECTORY
0944: && SVNPathUtil.isChildOf(srcParent,
0945: dstParent)) {
0946: adminArea = wcAccess.retrieve(dstParent);
0947: } else {
0948: adminArea = wcAccess.open(dstParent, true, 0);
0949: }
0950: }
0951:
0952: if (!force) {
0953: try {
0954: SVNWCManager.canDelete(srcPath, getOptions(),
0955: this );
0956: } catch (SVNException svne) {
0957: SVNErrorMessage err = svne
0958: .getErrorMessage()
0959: .wrap(
0960: "Move will not be attempted unless forced");
0961: SVNErrorManager.error(err, svne);
0962: }
0963: }
0964: } else {
0965: adminArea = wcAccess.open(dstParent, true, 0);
0966: }
0967:
0968: SVNWCAccess copyAccess = createWCAccess();
0969: try {
0970: SVNAdminArea srcArea = copyAccess.probeOpen(srcPath,
0971: false, SVNWCAccess.INFINITE_DEPTH);
0972: SVNEntry dstEntry = adminArea.getEntry(adminArea
0973: .getThisDirName(), false);
0974: if (dstEntry == null) {
0975: SVNErrorMessage err = SVNErrorMessage.create(
0976: SVNErrorCode.ENTRY_NOT_FOUND,
0977: "''{0}'' is not under version control",
0978: adminArea.getRoot());
0979: SVNErrorManager.error(err);
0980: }
0981:
0982: SVNEntry srcEntry = copyAccess.getEntry(srcPath, false);
0983: if (srcEntry == null) {
0984: SVNErrorMessage err = SVNErrorMessage.create(
0985: SVNErrorCode.ENTRY_NOT_FOUND,
0986: "''{0}'' is not under version control",
0987: srcPath);
0988: SVNErrorManager.error(err);
0989: }
0990:
0991: if (srcEntry.getRepositoryRoot() != null
0992: && dstEntry.getRepositoryRoot() != null
0993: && !srcEntry.getRepositoryRoot().equals(
0994: dstEntry.getRepositoryRoot())) {
0995: SVNErrorMessage err = SVNErrorMessage
0996: .create(
0997: SVNErrorCode.WC_INVALID_SCHEDULE,
0998: "Cannot copy to ''{0}'', as it is not from repository ''{1}''; it is from ''{2}''",
0999: new Object[] {
1000: adminArea.getRoot(),
1001: srcEntry
1002: .getRepositoryRoot(),
1003: dstEntry
1004: .getRepositoryRoot() });
1005: SVNErrorManager.error(err);
1006: }
1007:
1008: if (dstEntry.isScheduledForDeletion()) {
1009: SVNErrorMessage err = SVNErrorMessage
1010: .create(
1011: SVNErrorCode.WC_INVALID_SCHEDULE,
1012: "Cannot copy to ''{0}'' as it is scheduled for deletion",
1013: adminArea.getRoot());
1014: SVNErrorManager.error(err);
1015: }
1016:
1017: if (srcType == SVNFileType.FILE) {
1018: copyFile(adminArea, srcArea, srcPath, dstPath
1019: .getName());
1020: } else if (srcType == SVNFileType.DIRECTORY) {
1021: copyDir(adminArea, srcArea, srcPath, dstPath
1022: .getName());
1023: }
1024: } finally {
1025: copyAccess.close();
1026: }
1027: if (isMove) {
1028: SVNWCManager.delete(srcParentArea.getWCAccess(),
1029: srcParentArea, srcPath, true, false);
1030: }
1031: } finally {
1032: wcAccess.close();
1033: }
1034:
1035: }
1036:
1037: private void copyFile(SVNAdminArea dstParent, SVNAdminArea srcArea,
1038: File srcPath, String dstName) throws SVNException {
1039: SVNWCAccess wcAccess = dstParent.getWCAccess();
1040: File dstPath = dstParent.getFile(dstName);
1041:
1042: if (SVNFileType.getType(dstPath) != SVNFileType.NONE) {
1043: SVNErrorMessage err = SVNErrorMessage
1044: .create(SVNErrorCode.ENTRY_EXISTS,
1045: "''{0}'' already exists and is in the way",
1046: dstPath);
1047: SVNErrorManager.error(err);
1048: }
1049:
1050: SVNEntry dstEntry = wcAccess.getEntry(dstPath, false);
1051: if (dstEntry != null && dstEntry.isFile()) {
1052: if (!dstEntry.isScheduledForDeletion()) {
1053: SVNErrorMessage err = SVNErrorMessage.create(
1054: SVNErrorCode.ENTRY_EXISTS,
1055: "There is already a versioned item ''{0}''",
1056: dstPath);
1057: SVNErrorManager.error(err);
1058: }
1059: }
1060: SVNEntry srcEntry = srcArea.getWCAccess().getEntry(srcPath,
1061: false);
1062: if (srcEntry == null) {
1063: SVNErrorMessage err = SVNErrorMessage
1064: .create(
1065: SVNErrorCode.UNVERSIONED_RESOURCE,
1066: "Cannot copy or move ''{0}'': it''s not under version control",
1067: srcPath);
1068: SVNErrorManager.error(err);
1069: }
1070: if (srcEntry.isScheduledForAddition()
1071: || srcEntry.getURL() == null || srcEntry.isCopied()) {
1072: SVNErrorMessage err = SVNErrorMessage
1073: .create(
1074: SVNErrorCode.UNSUPPORTED_FEATURE,
1075: "Cannot copy or move ''{0}'': it''s not in repository yet; try committing first",
1076: srcPath);
1077: SVNErrorManager.error(err);
1078: }
1079: File textBase = srcArea.getBaseFile(srcPath.getName(), false);
1080: File tmpTextBase = dstParent.getBaseFile(dstName, true);
1081: String copyFromURL = srcEntry.getURL();
1082: long copyFromRevision = srcEntry.getRevision();
1083:
1084: Map baseProperties = srcArea.getBaseProperties(
1085: srcEntry.getName()).asMap();
1086: Map properties = srcArea.getProperties(srcEntry.getName())
1087: .asMap();
1088:
1089: SVNFileUtil.copyFile(textBase, tmpTextBase, false);
1090: File tmpFile = SVNFileUtil.createUniqueFile(
1091: dstParent.getRoot(), ".copy", ".tmp");
1092: SVNFileUtil.copy(srcArea.getFile(srcEntry.getName()), tmpFile,
1093: false, false);
1094:
1095: SVNWCManager.addRepositoryFile(dstParent, dstName, tmpFile,
1096: tmpTextBase, baseProperties, properties, copyFromURL,
1097: copyFromRevision);
1098:
1099: SVNEvent event = SVNEventFactory.createAddedEvent(dstParent,
1100: dstName, SVNNodeKind.FILE, null);
1101: dstParent.getWCAccess().handleEvent(event);
1102:
1103: }
1104:
1105: private void copyDir(SVNAdminArea dstParent, SVNAdminArea srcArea,
1106: File srcPath, String dstName) throws SVNException {
1107: SVNEntry srcEntry = srcArea.getWCAccess().getEntry(srcPath,
1108: false);
1109: if (srcEntry == null) {
1110: SVNErrorMessage err = SVNErrorMessage.create(
1111: SVNErrorCode.ENTRY_NOT_FOUND,
1112: "'{0}'' is not under version control", srcPath);
1113: SVNErrorManager.error(err);
1114: }
1115: if (srcEntry.isScheduledForAddition()
1116: || srcEntry.getURL() == null || srcEntry.isCopied()) {
1117: SVNErrorMessage err = SVNErrorMessage
1118: .create(
1119: SVNErrorCode.UNSUPPORTED_FEATURE,
1120: "Cannot copy or move ''{0}'': it''s not in repository yet; try committing first",
1121: srcPath);
1122: SVNErrorManager.error(err);
1123: }
1124:
1125: File dstPath = dstParent.getFile(dstName);
1126: SVNFileUtil.copyDirectory(srcPath, dstPath, true, srcArea
1127: .getWCAccess());
1128: SVNWCClient wcClient = new SVNWCClient(
1129: (ISVNAuthenticationManager) null, (ISVNOptions) null);
1130: wcClient.doCleanup(dstPath);
1131:
1132: SVNWCAccess tmpAccess = SVNWCAccess.newInstance(null);
1133: try {
1134: SVNAdminArea tmpDir = tmpAccess.open(dstPath, true,
1135: SVNWCAccess.INFINITE_DEPTH);
1136: postCopyCleanup(tmpDir);
1137: } finally {
1138: tmpAccess.close();
1139: }
1140: SVNWCManager.add(dstPath, dstParent, srcEntry.getSVNURL(),
1141: srcEntry.getRevision());
1142: }
1143:
1144: static void postCopyCleanup(SVNAdminArea dir) throws SVNException {
1145: SVNPropertiesManager.deleteWCProperties(dir, null, false);
1146: SVNFileUtil.setHidden(dir.getAdminDirectory(), true);
1147:
1148: for (Iterator entries = dir.entries(true); entries.hasNext();) {
1149: SVNEntry entry = (SVNEntry) entries.next();
1150: boolean deleted = entry.isDeleted();
1151: SVNNodeKind kind = entry.getKind();
1152:
1153: if (entry.isDeleted()) {
1154: entry.setSchedule(SVNProperty.SCHEDULE_DELETE);
1155: entry.setDeleted(false);
1156: if (entry.isDirectory()) {
1157: entry.setKind(SVNNodeKind.FILE);
1158: }
1159: }
1160: if (entry.getLockToken() != null) {
1161: entry.setLockToken(null);
1162: entry.setLockOwner(null);
1163: entry.setLockCreationDate(null);
1164: }
1165: if (!deleted && kind == SVNNodeKind.DIR
1166: && !dir.getThisDirName().equals(entry.getName())) {
1167: SVNAdminArea childDir = dir.getWCAccess().retrieve(
1168: dir.getFile(entry.getName()));
1169: postCopyCleanup(childDir);
1170: }
1171: }
1172: dir.saveEntries(false);
1173: }
1174:
1175: private static class CopyCommitPathHandler implements
1176: ISVNCommitPathHandler {
1177:
1178: private String mySrcPath;
1179: private String myDstPath;
1180: private long mySrcRev;
1181: private boolean myIsMove;
1182: private boolean myIsResurrect;
1183: private SVNNodeKind mySrcKind;
1184:
1185: public CopyCommitPathHandler(String srcPath, long srcRev,
1186: SVNNodeKind srcKind, String dstPath, boolean isMove,
1187: boolean isRessurect) {
1188: mySrcPath = srcPath;
1189: myDstPath = dstPath;
1190: mySrcRev = srcRev;
1191: myIsMove = isMove;
1192: mySrcKind = srcKind;
1193: myIsResurrect = isRessurect;
1194: }
1195:
1196: public boolean handleCommitPath(String commitPath,
1197: ISVNEditor commitEditor) throws SVNException {
1198: boolean doAdd = false;
1199: boolean doDelete = false;
1200: if (myIsResurrect) {
1201: if (!myIsMove) {
1202: doAdd = true;
1203: }
1204: } else {
1205: if (myIsMove) {
1206: if (commitPath.equals(mySrcPath)) {
1207: doDelete = true;
1208: } else {
1209: doAdd = true;
1210: }
1211: } else {
1212: doAdd = true;
1213: }
1214: }
1215: if (doDelete) {
1216: commitEditor.deleteEntry(mySrcPath, -1);
1217: }
1218: boolean closeDir = false;
1219: if (doAdd) {
1220: if (mySrcKind == SVNNodeKind.DIR) {
1221: commitEditor.addDir(myDstPath, mySrcPath, mySrcRev);
1222: closeDir = true;
1223: } else {
1224: commitEditor
1225: .addFile(myDstPath, mySrcPath, mySrcRev);
1226: commitEditor.closeFile(myDstPath, null);
1227: }
1228: }
1229: return closeDir;
1230: }
1231: }
1232: }
|