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.HashMap;
0017: import java.util.Iterator;
0018: import java.util.Map;
0019: import java.util.StringTokenizer;
0020:
0021: import org.tmatesoft.svn.core.SVNCancelException;
0022: import org.tmatesoft.svn.core.SVNErrorCode;
0023: import org.tmatesoft.svn.core.SVNErrorMessage;
0024: import org.tmatesoft.svn.core.SVNException;
0025: import org.tmatesoft.svn.core.SVNNodeKind;
0026: import org.tmatesoft.svn.core.SVNProperty;
0027: import org.tmatesoft.svn.core.SVNURL;
0028: import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
0029: import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
0030: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
0031: import org.tmatesoft.svn.core.internal.util.SVNTimeUtil;
0032: import org.tmatesoft.svn.core.internal.wc.SVNCancellableEditor;
0033: import org.tmatesoft.svn.core.internal.wc.SVNCancellableOutputStream;
0034: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
0035: import org.tmatesoft.svn.core.internal.wc.SVNEventFactory;
0036: import org.tmatesoft.svn.core.internal.wc.SVNExportEditor;
0037: import org.tmatesoft.svn.core.internal.wc.SVNExternalInfo;
0038: import org.tmatesoft.svn.core.internal.wc.SVNFileType;
0039: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
0040: import org.tmatesoft.svn.core.internal.wc.SVNPropertiesManager;
0041: import org.tmatesoft.svn.core.internal.wc.SVNUpdateEditor;
0042: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
0043: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory;
0044: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaInfo;
0045: import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
0046: import org.tmatesoft.svn.core.internal.wc.admin.SVNReporter;
0047: import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator;
0048: import org.tmatesoft.svn.core.internal.wc.admin.SVNVersionedProperties;
0049: import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
0050: import org.tmatesoft.svn.core.io.ISVNReporter;
0051: import org.tmatesoft.svn.core.io.ISVNReporterBaton;
0052: import org.tmatesoft.svn.core.io.SVNRepository;
0053:
0054: /**
0055: * This class provides methods which allow to check out, update, switch and relocate a
0056: * Working Copy as well as export an unversioned directory or file from a repository.
0057: *
0058: * <p>
0059: * Here's a list of the <b>SVNUpdateClient</b>'s methods
0060: * matched against corresponing commands of the SVN command line
0061: * client:
0062: *
0063: * <table cellpadding="3" cellspacing="1" border="0" width="40%" bgcolor="#999933">
0064: * <tr bgcolor="#ADB8D9" align="left">
0065: * <td><b>SVNKit</b></td>
0066: * <td><b>Subversion</b></td>
0067: * </tr>
0068: * <tr bgcolor="#EAEAEA" align="left">
0069: * <td>doCheckout()</td><td>'svn checkout'</td>
0070: * </tr>
0071: * <tr bgcolor="#EAEAEA" align="left">
0072: * <td>doUpdate()</td><td>'svn update'</td>
0073: * </tr>
0074: * <tr bgcolor="#EAEAEA" align="left">
0075: * <td>doSwitch()</td><td>'svn switch'</td>
0076: * </tr>
0077: * <tr bgcolor="#EAEAEA" align="left">
0078: * <td>doRelocate()</td><td>'svn switch --relocate oldURL newURL'</td>
0079: * </tr>
0080: * <tr bgcolor="#EAEAEA" align="left">
0081: * <td>doExport()</td><td>'svn export'</td>
0082: * </tr>
0083: * </table>
0084: *
0085: * @version 1.1.1
0086: * @author TMate Software Ltd.
0087: * @see <a target="_top" href="http://svnkit.com/kb/examples/">Examples</a>
0088: */
0089: public class SVNUpdateClient extends SVNBasicClient {
0090:
0091: /**
0092: * Constructs and initializes an <b>SVNUpdateClient</b> object
0093: * with the specified run-time configuration and authentication
0094: * drivers.
0095: *
0096: * <p>
0097: * If <code>options</code> is <span class="javakeyword">null</span>,
0098: * then this <b>SVNUpdateClient</b> will be using a default run-time
0099: * configuration driver which takes client-side settings from the
0100: * default SVN's run-time configuration area but is not able to
0101: * change those settings (read more on {@link ISVNOptions} and {@link SVNWCUtil}).
0102: *
0103: * <p>
0104: * If <code>authManager</code> is <span class="javakeyword">null</span>,
0105: * then this <b>SVNUpdateClient</b> will be using a default authentication
0106: * and network layers driver (see {@link SVNWCUtil#createDefaultAuthenticationManager()})
0107: * which uses server-side settings and auth storage from the
0108: * default SVN's run-time configuration area (or system properties
0109: * if that area is not found).
0110: *
0111: * @param authManager an authentication and network layers driver
0112: * @param options a run-time configuration options driver
0113: */
0114: public SVNUpdateClient(ISVNAuthenticationManager authManager,
0115: ISVNOptions options) {
0116: super (authManager, options);
0117: }
0118:
0119: public SVNUpdateClient(ISVNRepositoryPool repositoryPool,
0120: ISVNOptions options) {
0121: super (repositoryPool, options);
0122: }
0123:
0124: /**
0125: * Brings the Working Copy item up-to-date with repository changes at the specified
0126: * revision.
0127: *
0128: * <p>
0129: * As a revision <b>SVNRevision</b>'s pre-defined constant fields can be used. For example,
0130: * to update the Working Copy to the latest revision of the repository use
0131: * {@link SVNRevision#HEAD HEAD}.
0132: *
0133: * @param file the Working copy item to be updated
0134: * @param revision the desired revision against which the item will be updated
0135: * @param recursive if <span class="javakeyword">true</span> and <code>file</code> is
0136: * a directory then the entire tree will be updated, otherwise if
0137: * <span class="javakeyword">false</span> - only items located immediately
0138: * in the directory itself
0139: * @return the revision number to which <code>file</code> was updated to
0140: * @throws SVNException
0141: */
0142: public long doUpdate(File file, SVNRevision revision,
0143: boolean recursive) throws SVNException {
0144: file = new File(SVNPathUtil.validateFilePath(file
0145: .getAbsolutePath()));
0146: SVNWCAccess wcAccess = createWCAccess();
0147: SVNAdminAreaInfo adminInfo = null;
0148: try {
0149: adminInfo = wcAccess.openAnchor(file, true,
0150: recursive ? SVNWCAccess.INFINITE_DEPTH : 0);
0151: SVNAdminArea anchorArea = adminInfo.getAnchor();
0152: final SVNReporter reporter = new SVNReporter(adminInfo,
0153: file, true, recursive, getDebugLog());
0154:
0155: SVNEntry entry = anchorArea.getEntry(anchorArea
0156: .getThisDirName(), false);
0157: SVNURL url = entry.getSVNURL();
0158: if (url == null) {
0159: SVNErrorMessage err = SVNErrorMessage.create(
0160: SVNErrorCode.ENTRY_MISSING_URL,
0161: "Entry ''{0}'' has no URL", anchorArea
0162: .getRoot());
0163: SVNErrorManager.error(err);
0164: }
0165: SVNUpdateEditor editor = new SVNUpdateEditor(adminInfo,
0166: null, recursive, isLeaveConflictsUnresolved());
0167: SVNRepository repos = createRepository(url, true);
0168:
0169: String target = "".equals(adminInfo.getTargetName()) ? null
0170: : adminInfo.getTargetName();
0171: long revNumber = getRevisionNumber(revision, repos, file);
0172: SVNURL reposRoot = repos.getRepositoryRoot(true);
0173: wcAccess.setRepositoryRoot(file, reposRoot);
0174: repos.update(revNumber, target, recursive, reporter,
0175: SVNCancellableEditor.newInstance(editor, this ,
0176: getDebugLog()));
0177:
0178: if (editor.getTargetRevision() >= 0) {
0179: if (recursive && !isIgnoreExternals()) {
0180: handleExternals(adminInfo);
0181: }
0182: dispatchEvent(SVNEventFactory
0183: .createUpdateCompletedEvent(adminInfo, editor
0184: .getTargetRevision()));
0185: }
0186: return editor.getTargetRevision();
0187: } finally {
0188: wcAccess.close();
0189: sleepForTimeStamp();
0190: }
0191: }
0192:
0193: /**
0194: * Updates the Working Copy item to mirror a new URL.
0195: *
0196: * <p>
0197: * As a revision <b>SVNRevision</b>'s pre-defined constant fields can be used. For example,
0198: * to update the Working Copy to the latest revision of the repository use
0199: * {@link SVNRevision#HEAD HEAD}.
0200: *
0201: * <p>
0202: * Calling this method is equivalent to
0203: * <code>doSwitch(file, url, SVNRevision.UNDEFINED, revision, recursive)</code>.
0204: *
0205: * @param file the Working copy item to be switched
0206: * @param url the repository location as a target against which the item will
0207: * be switched
0208: * @param revision the desired revision of the repository target
0209: * @param recursive if <span class="javakeyword">true</span> and <code>file</code> is
0210: * a directory then the entire tree will be updated, otherwise if
0211: * <span class="javakeyword">false</span> - only items located immediately
0212: * in the directory itself
0213: * @return the revision number to which <code>file</code> was updated to
0214: * @throws SVNException
0215: */
0216: public long doSwitch(File file, SVNURL url, SVNRevision revision,
0217: boolean recursive) throws SVNException {
0218: return doSwitch(file, url, SVNRevision.UNDEFINED, revision,
0219: recursive);
0220: }
0221:
0222: /**
0223: * Updates the Working Copy item to mirror a new URL.
0224: *
0225: * <p>
0226: * As a revision <b>SVNRevision</b>'s pre-defined constant fields can be used. For example,
0227: * to update the Working Copy to the latest revision of the repository use
0228: * {@link SVNRevision#HEAD HEAD}.
0229: *
0230: * @param file the Working copy item to be switched
0231: * @param url the repository location as a target against which the item will
0232: * be switched
0233: * @param pegRevision a revision in which <code>file</code> is first looked up
0234: * in the repository
0235: * @param revision the desired revision of the repository target
0236: * @param recursive if <span class="javakeyword">true</span> and <code>file</code> is
0237: * a directory then the entire tree will be updated, otherwise if
0238: * <span class="javakeyword">false</span> - only items located immediately
0239: * in the directory itself
0240: * @return the revision number to which <code>file</code> was updated to
0241: * @throws SVNException
0242: */
0243: public long doSwitch(File file, SVNURL url,
0244: SVNRevision pegRevision, SVNRevision revision,
0245: boolean recursive) throws SVNException {
0246: SVNWCAccess wcAccess = createWCAccess();
0247: try {
0248: SVNAdminAreaInfo info = wcAccess.openAnchor(file, true,
0249: SVNWCAccess.INFINITE_DEPTH);
0250: final SVNReporter reporter = new SVNReporter(info, file,
0251: true, recursive, getDebugLog());
0252: SVNAdminArea anchorArea = info.getAnchor();
0253: SVNEntry entry = anchorArea.getEntry(anchorArea
0254: .getThisDirName(), false);
0255: if (entry == null) {
0256: SVNErrorMessage err = SVNErrorMessage.create(
0257: SVNErrorCode.UNVERSIONED_RESOURCE,
0258: "''{0}'' is not under version control",
0259: anchorArea.getRoot());
0260: SVNErrorManager.error(err);
0261: }
0262: SVNURL sourceURL = entry.getSVNURL();
0263: if (sourceURL == null) {
0264: SVNErrorMessage err = SVNErrorMessage.create(
0265: SVNErrorCode.ENTRY_MISSING_URL,
0266: "Directory ''{0}'' has no URL", anchorArea
0267: .getRoot());
0268: SVNErrorManager.error(err);
0269: }
0270: SVNRepository repository = createRepository(sourceURL, true);
0271: long revNumber = getRevisionNumber(revision, repository,
0272: file);
0273: if (pegRevision != null && pegRevision.isValid()) {
0274: SVNRepositoryLocation[] locs = getLocations(url, null,
0275: null, pegRevision, SVNRevision
0276: .create(revNumber),
0277: SVNRevision.UNDEFINED);
0278: url = locs[0].getURL();
0279: }
0280:
0281: SVNUpdateEditor editor = new SVNUpdateEditor(info, url
0282: .toString(), recursive,
0283: isLeaveConflictsUnresolved());
0284: String target = "".equals(info.getTargetName()) ? null
0285: : info.getTargetName();
0286: repository.update(url, revNumber, target, recursive,
0287: reporter, SVNCancellableEditor.newInstance(editor,
0288: this , getDebugLog()));
0289:
0290: if (editor.getTargetRevision() >= 0 && recursive
0291: && !isIgnoreExternals()) {
0292: handleExternals(info);
0293: dispatchEvent(SVNEventFactory
0294: .createUpdateCompletedEvent(info, editor
0295: .getTargetRevision()));
0296: }
0297: return editor.getTargetRevision();
0298: } finally {
0299: wcAccess.close();
0300: sleepForTimeStamp();
0301: }
0302: }
0303:
0304: /**
0305: * Checks out a Working Copy from a repository.
0306: *
0307: * <p>
0308: * If the destination path (<code>dstPath</code>) is <span class="javakeyword">null</span>
0309: * then the last component of <code>url</code> is used for the local directory name.
0310: *
0311: * <p>
0312: * As a revision <b>SVNRevision</b>'s pre-defined constant fields can be used. For example,
0313: * to check out a Working Copy at the latest revision of the repository use
0314: * {@link SVNRevision#HEAD HEAD}.
0315: *
0316: * @param url a repository location from where a Working Copy will be checked out
0317: * @param dstPath the local path where the Working Copy will be placed
0318: * @param pegRevision the revision at which <code>url</code> will be firstly seen
0319: * in the repository to make sure it's the one that is needed
0320: * @param revision the desired revision of the Working Copy to be checked out
0321: * @param recursive if <span class="javakeyword">true</span> and <code>url</code> is
0322: * a directory then the entire tree will be checked out, otherwise if
0323: * <span class="javakeyword">false</span> - only items located immediately
0324: * in the directory itself
0325: * @return the revision number of the Working Copy
0326: * @throws SVNException <code>url</code> refers to a file, not a directory; <code>dstPath</code>
0327: * already exists but it is a file, not a directory; <code>dstPath</code> already
0328: * exists and is a versioned directory but has a different URL (repository location
0329: * against which the directory is controlled)
0330: */
0331: public long doCheckout(SVNURL url, File dstPath,
0332: SVNRevision pegRevision, SVNRevision revision,
0333: boolean recursive) throws SVNException {
0334: if (dstPath == null) {
0335: SVNErrorMessage err = SVNErrorMessage.create(
0336: SVNErrorCode.BAD_FILENAME,
0337: "Checkout destination path can not be NULL");
0338: SVNErrorManager.error(err);
0339: }
0340: pegRevision = pegRevision == null ? SVNRevision.UNDEFINED
0341: : pegRevision;
0342:
0343: if (!revision.isValid() && pegRevision.isValid()) {
0344: revision = pegRevision;
0345: }
0346:
0347: if (!revision.isValid()) {
0348: revision = SVNRevision.HEAD;
0349: }
0350:
0351: SVNRepository repos = createRepository(url, null, pegRevision,
0352: revision);
0353: url = repos.getLocation();
0354: long revNumber = getRevisionNumber(revision, repos, null);
0355: SVNNodeKind targetNodeKind = repos.checkPath("", revNumber);
0356: if (targetNodeKind == SVNNodeKind.FILE) {
0357: SVNErrorMessage err = SVNErrorMessage.create(
0358: SVNErrorCode.UNSUPPORTED_FEATURE,
0359: "URL ''{0}'' refers to a file, not a directory",
0360: url);
0361: SVNErrorManager.error(err);
0362: } else if (targetNodeKind == SVNNodeKind.NONE) {
0363: SVNErrorMessage err = SVNErrorMessage.create(
0364: SVNErrorCode.RA_ILLEGAL_URL,
0365: "URL ''{0}'' doesn''t exist", url);
0366: SVNErrorManager.error(err);
0367: }
0368: String uuid = repos.getRepositoryUUID(true);
0369: SVNURL repositoryRoot = repos.getRepositoryRoot(true);
0370:
0371: long result = -1;
0372: try {
0373: SVNWCAccess wcAccess = createWCAccess();
0374: SVNFileType kind = SVNFileType.getType(dstPath);
0375: if (kind == SVNFileType.NONE) {
0376: SVNAdminAreaFactory.createVersionedDirectory(dstPath,
0377: url, repositoryRoot, uuid, revNumber);
0378: result = doUpdate(dstPath, revision, recursive);
0379: } else if (kind == SVNFileType.DIRECTORY) {
0380: int formatVersion = SVNAdminAreaFactory.checkWC(
0381: dstPath, true);
0382: if (formatVersion != 0) {
0383: SVNAdminArea adminArea = wcAccess.open(dstPath,
0384: false, 0);
0385: SVNEntry rootEntry = adminArea.getEntry(adminArea
0386: .getThisDirName(), false);
0387: wcAccess.closeAdminArea(dstPath);
0388: if (rootEntry.getSVNURL() != null
0389: && url.equals(rootEntry.getSVNURL())) {
0390: result = doUpdate(dstPath, revision, recursive);
0391: } else {
0392: String message = "''{0}'' is already a working copy for a different URL";
0393: if (rootEntry.isIncomplete()) {
0394: message += "; perform update to complete it";
0395: }
0396: SVNErrorMessage err = SVNErrorMessage.create(
0397: SVNErrorCode.WC_OBSTRUCTED_UPDATE,
0398: message, dstPath);
0399: SVNErrorManager.error(err);
0400: }
0401: } else {
0402: SVNAdminAreaFactory.createVersionedDirectory(
0403: dstPath, url, repositoryRoot, uuid,
0404: revNumber);
0405: result = doUpdate(dstPath, revision, recursive);
0406: }
0407: } else {
0408: SVNErrorMessage err = SVNErrorMessage
0409: .create(
0410: SVNErrorCode.WC_NODE_KIND_CHANGE,
0411: "''{0}'' already exists and is not a directory",
0412: dstPath);
0413: SVNErrorManager.error(err);
0414: }
0415: } finally {
0416: sleepForTimeStamp();
0417: }
0418: return result;
0419: }
0420:
0421: /**
0422: * Exports a clean directory or single file from a repository.
0423: *
0424: * <p>
0425: * If <code>eolStyle</code> is not <span class="javakeyword">null</span> then it should denote
0426: * a specific End-Of-Line marker for the files to be exported. Significant values for
0427: * <code>eolStyle</code> are:
0428: * <ul>
0429: * <li>"CRLF" (Carriage Return Line Feed) - this causes files to contain '\r\n' line ending sequences
0430: * for EOL markers, regardless of the operating system in use (for instance, this EOL marker is used by
0431: * software on the Windows platform).
0432: * <li>"LF" (Line Feed) - this causes files to contain '\n' line ending sequences
0433: * for EOL markers, regardless of the operating system in use (for instance, this EOL marker is used by
0434: * software on the Unix platform).
0435: * <li>"CR" (Carriage Return) - this causes files to contain '\r' line ending sequences
0436: * for EOL markers, regardless of the operating system in use (for instance, this EOL marker was used by
0437: * software on older Macintosh platforms).
0438: * <li>"native" - this causes files to contain the EOL markers that are native to the operating system
0439: * on which SVNKit is run.
0440: * </ul>
0441: *
0442: * @param url a repository location from where the unversioned directory/file will
0443: * be exported
0444: * @param dstPath the local path where the repository items will be exported to
0445: * @param pegRevision the revision at which <code>url</code> will be firstly seen
0446: * in the repository to make sure it's the one that is needed
0447: * @param revision the desired revision of the directory/file to be exported
0448: * @param eolStyle a string that denotes a specific End-Of-Line charecter;
0449: * @param force <span class="javakeyword">true</span> to fore the operation even
0450: * if there are local files with the same names as those in the repository
0451: * (local ones will be replaced)
0452: * @param recursive if <span class="javakeyword">true</span> and <code>url</code> is
0453: * a directory then the entire tree will be exported, otherwise if
0454: * <span class="javakeyword">false</span> - only items located immediately
0455: * in the directory itself
0456: * @return the revision number of the exported directory/file
0457: * @throws SVNException
0458: */
0459: public long doExport(SVNURL url, File dstPath,
0460: SVNRevision pegRevision, SVNRevision revision,
0461: String eolStyle, boolean force, boolean recursive)
0462: throws SVNException {
0463: SVNRepository repository = createRepository(url, null,
0464: pegRevision, revision);
0465: long revisionNumber = getRevisionNumber(revision, repository,
0466: null);
0467: long exportedRevision = doRemoteExport(repository,
0468: revisionNumber, dstPath, eolStyle, force, recursive);
0469: dispatchEvent(SVNEventFactory.createUpdateCompletedEvent(
0470: (SVNAdminAreaInfo) null, exportedRevision));
0471: return exportedRevision;
0472: }
0473:
0474: /**
0475: * Exports a clean directory or single file from eihter a source Working Copy or
0476: * a repository.
0477: *
0478: * <p>
0479: * How this method works:
0480: * <ul>
0481: * <li> If <code>revision</code> is different from {@link SVNRevision#BASE BASE},
0482: * {@link SVNRevision#WORKING WORKING}, {@link SVNRevision#COMMITTED COMMITTED},
0483: * {@link SVNRevision#UNDEFINED UNDEFINED} - then the repository origin of <code>srcPath</code>
0484: * will be exported (what is done by "remote" {@link #doExport(SVNURL, File, SVNRevision, SVNRevision, String, boolean, boolean)
0485: * doExport()}).
0486: * <li> In other cases a clean unversioned copy of <code>srcPath</code> - either a directory or a single file -
0487: * is exported to <code>dstPath</code>.
0488: * </ul>
0489: *
0490: * <p>
0491: * If <code>eolStyle</code> is not <span class="javakeyword">null</span> then it should denote
0492: * a specific End-Of-Line marker for the files to be exported. Significant values for
0493: * <code>eolStyle</code> are:
0494: * <ul>
0495: * <li>"CRLF" (Carriage Return Line Feed) - this causes files to contain '\r\n' line ending sequences
0496: * for EOL markers, regardless of the operating system in use (for instance, this EOL marker is used by
0497: * software on the Windows platform).
0498: * <li>"LF" (Line Feed) - this causes files to contain '\n' line ending sequences
0499: * for EOL markers, regardless of the operating system in use (for instance, this EOL marker is used by
0500: * software on the Unix platform).
0501: * <li>"CR" (Carriage Return) - this causes files to contain '\r' line ending sequences
0502: * for EOL markers, regardless of the operating system in use (for instance, this EOL marker was used by
0503: * software on older Macintosh platforms).
0504: * <li>"native" - this causes files to contain the EOL markers that are native to the operating system
0505: * on which SVNKit is run.
0506: * </ul>
0507: *
0508: * @param srcPath a repository location from where the unversioned directory/file will
0509: * be exported
0510: * @param dstPath the local path where the repository items will be exported to
0511: * @param pegRevision the revision at which <code>url</code> will be firstly seen
0512: * in the repository to make sure it's the one that is needed
0513: * @param revision the desired revision of the directory/file to be exported
0514: * @param eolStyle a string that denotes a specific End-Of-Line charecter;
0515: * @param force <span class="javakeyword">true</span> to fore the operation even
0516: * if there are local files with the same names as those in the repository
0517: * (local ones will be replaced)
0518: * @param recursive if <span class="javakeyword">true</span> and <code>url</code> is
0519: * a directory then the entire tree will be exported, otherwise if
0520: * <span class="javakeyword">false</span> - only items located immediately
0521: * in the directory itself
0522: * @return the revision number of the exported directory/file
0523: * @throws SVNException
0524: */
0525: public long doExport(File srcPath, final File dstPath,
0526: SVNRevision pegRevision, SVNRevision revision,
0527: String eolStyle, final boolean force, boolean recursive)
0528: throws SVNException {
0529: long exportedRevision = -1;
0530: if (revision != SVNRevision.BASE
0531: && revision != SVNRevision.WORKING
0532: && revision != SVNRevision.COMMITTED
0533: && revision != SVNRevision.UNDEFINED) {
0534: SVNRepository repository = createRepository(null, srcPath,
0535: pegRevision, revision);
0536: long revisionNumber = getRevisionNumber(revision,
0537: repository, srcPath);
0538: exportedRevision = doRemoteExport(repository,
0539: revisionNumber, dstPath, eolStyle, force, recursive);
0540: } else {
0541: if (revision == SVNRevision.UNDEFINED) {
0542: revision = SVNRevision.WORKING;
0543: }
0544: copyVersionedDir(srcPath, dstPath, revision, eolStyle,
0545: force, recursive);
0546: }
0547: dispatchEvent(SVNEventFactory.createUpdateCompletedEvent(
0548: (SVNAdminAreaInfo) null, exportedRevision));
0549: return exportedRevision;
0550: }
0551:
0552: private void copyVersionedDir(File from, File to,
0553: SVNRevision revision, String eolStyle, boolean force,
0554: boolean recursive) throws SVNException {
0555: SVNWCAccess wcAccess = createWCAccess();
0556: SVNAdminArea adminArea = wcAccess.probeOpen(from, false, 0);
0557: SVNEntry entry = wcAccess.getEntry(from, false);
0558: if (entry == null) {
0559: wcAccess.close();
0560: SVNErrorMessage err = SVNErrorMessage
0561: .create(
0562: SVNErrorCode.ENTRY_NOT_FOUND,
0563: "''{0}'' is not under version control or doesn''t exist",
0564: from, SVNErrorMessage.TYPE_WARNING);
0565: SVNErrorManager.error(err);
0566: }
0567:
0568: if (revision == SVNRevision.WORKING
0569: && entry.isScheduledForDeletion()) {
0570: return;
0571: }
0572: if (revision != SVNRevision.WORKING
0573: && entry.isScheduledForAddition()) {
0574: return;
0575: }
0576: if (entry.isDirectory()) {
0577: // create dir
0578: boolean dirCreated = to.mkdirs();
0579: if (!to.exists() || to.isFile()) {
0580: SVNErrorMessage err = SVNErrorMessage.create(
0581: SVNErrorCode.IO_ERROR,
0582: "Cannot create directory ''{0}''", to);
0583: SVNErrorManager.error(err);
0584: }
0585: if (!dirCreated && to.isDirectory() && !force) {
0586: SVNErrorMessage err = SVNErrorMessage
0587: .create(
0588: SVNErrorCode.WC_OBSTRUCTED_UPDATE,
0589: "''{0}'' already exists and will not be owerwritten unless forced",
0590: to);
0591: SVNErrorManager.error(err);
0592: }
0593: // read entries
0594: for (Iterator ents = adminArea.entries(false); ents
0595: .hasNext();) {
0596: SVNEntry childEntry = (SVNEntry) ents.next();
0597: if (childEntry.isDirectory()) {
0598: if (adminArea.getThisDirName().equals(
0599: childEntry.getName())) {
0600: continue;
0601: } else if (recursive) {
0602: File childTo = new File(to, childEntry
0603: .getName());
0604: File childFrom = new File(from, childEntry
0605: .getName());
0606: copyVersionedDir(childFrom, childTo, revision,
0607: eolStyle, force, recursive);
0608: }
0609: } else if (childEntry.isFile()) {
0610: File childTo = new File(to, childEntry.getName());
0611: copyVersionedFile(childTo, adminArea, childEntry
0612: .getName(), revision, eolStyle);
0613: }
0614: }
0615: } else if (entry.isFile()) {
0616: copyVersionedFile(to, adminArea, entry.getName(), revision,
0617: eolStyle);
0618: }
0619:
0620: wcAccess.close();
0621: }
0622:
0623: private void copyVersionedFile(File dstPath,
0624: SVNAdminArea adminArea, String fileName,
0625: SVNRevision revision, String eol) throws SVNException {
0626: SVNEntry entry = adminArea.getEntry(fileName, false);
0627: if (revision == SVNRevision.WORKING
0628: && entry.isScheduledForDeletion()) {
0629: return;
0630: }
0631: if (revision != SVNRevision.WORKING
0632: && entry.isScheduledForAddition()) {
0633: return;
0634: }
0635: boolean modified = false;
0636: SVNVersionedProperties props = null;
0637: long timestamp;
0638: if (revision != SVNRevision.WORKING) {
0639: props = adminArea.getBaseProperties(fileName);
0640: } else {
0641: props = adminArea.getProperties(fileName);
0642: modified = adminArea.hasTextModifications(fileName, false);
0643: }
0644: boolean special = props.getPropertyValue(SVNProperty.SPECIAL) != null;
0645: boolean executable = props
0646: .getPropertyValue(SVNProperty.EXECUTABLE) != null;
0647: String keywords = props.getPropertyValue(SVNProperty.KEYWORDS);
0648: byte[] eols = eol != null ? SVNTranslator.getEOL(eol) : null;
0649: if (eols == null) {
0650: eol = props.getPropertyValue(SVNProperty.EOL_STYLE);
0651: eols = SVNTranslator.getWorkingEOL(eol);
0652: }
0653: if (modified && !special) {
0654: timestamp = adminArea.getFile(fileName).lastModified();
0655: } else {
0656: timestamp = SVNTimeUtil.parseDateAsLong(entry
0657: .getCommittedDate());
0658: }
0659: Map keywordsMap = null;
0660: if (keywords != null) {
0661: String rev = Long.toString(entry.getCommittedRevision());
0662: String author;
0663: if (modified) {
0664: author = "(local)";
0665: rev += "M";
0666: } else {
0667: author = entry.getAuthor();
0668: }
0669: keywordsMap = SVNTranslator.computeKeywords(keywords, entry
0670: .getURL(), author, entry.getCommittedDate(), rev,
0671: getOptions());
0672: }
0673: File srcFile = revision == SVNRevision.WORKING ? adminArea
0674: .getFile(fileName) : adminArea.getBaseFile(fileName,
0675: false);
0676: SVNFileType fileType = SVNFileType.getType(srcFile);
0677: if (fileType == SVNFileType.SYMLINK
0678: && revision == SVNRevision.WORKING) {
0679: // base will be translated OK, but working not.
0680: File tmpBaseFile = adminArea.getBaseFile(fileName, true);
0681: try {
0682: SVNTranslator.translate(srcFile, tmpBaseFile, eols,
0683: keywordsMap, special, false);
0684: SVNTranslator.translate(tmpBaseFile, dstPath, eols,
0685: keywordsMap, special, true);
0686: } finally {
0687: tmpBaseFile.delete();
0688: }
0689: } else {
0690: SVNTranslator.translate(srcFile, dstPath, eols,
0691: keywordsMap, special, true);
0692: }
0693: if (executable) {
0694: SVNFileUtil.setExecutable(dstPath, true);
0695: }
0696: if (!special && timestamp > 0) {
0697: dstPath.setLastModified(timestamp);
0698: }
0699: }
0700:
0701: private long doRemoteExport(SVNRepository repository,
0702: final long revNumber, File dstPath, String eolStyle,
0703: boolean force, boolean recursive) throws SVNException {
0704: SVNNodeKind dstKind = repository.checkPath("", revNumber);
0705: if (dstKind == SVNNodeKind.DIR) {
0706: SVNExportEditor editor = new SVNExportEditor(this ,
0707: repository.getLocation().toString(), dstPath,
0708: force, eolStyle, getOptions());
0709: repository.update(revNumber, null, recursive,
0710: new ISVNReporterBaton() {
0711: public void report(ISVNReporter reporter)
0712: throws SVNException {
0713: reporter.setPath("", null, revNumber, true);
0714: reporter.finishReport();
0715: }
0716: }, SVNCancellableEditor.newInstance(editor, this ,
0717: getDebugLog()));
0718: // nothing may be created.
0719: SVNFileType fileType = SVNFileType.getType(dstPath);
0720: if (fileType == SVNFileType.NONE) {
0721: editor.openRoot(revNumber);
0722: }
0723: if (!isIgnoreExternals() && recursive) {
0724: Map externals = editor.getCollectedExternals();
0725: for (Iterator files = externals.keySet().iterator(); files
0726: .hasNext();) {
0727: File rootFile = (File) files.next();
0728: String propValue = (String) externals.get(rootFile);
0729: if (propValue == null) {
0730: continue;
0731: }
0732: SVNExternalInfo[] infos = SVNAdminAreaInfo
0733: .parseExternals("", propValue);
0734: for (int i = 0; i < infos.length; i++) {
0735: File targetDir = new File(rootFile, infos[i]
0736: .getPath());
0737: SVNURL srcURL = infos[i].getOldURL();
0738: long externalRevNumber = infos[i]
0739: .getOldRevision();
0740: SVNRevision srcRevision = externalRevNumber >= 0 ? SVNRevision
0741: .create(externalRevNumber)
0742: : SVNRevision.HEAD;
0743: String relativePath = targetDir.equals(dstPath) ? ""
0744: : targetDir
0745: .getAbsolutePath()
0746: .substring(
0747: dstPath
0748: .getAbsolutePath()
0749: .length() + 1);
0750: relativePath = relativePath.replace(
0751: File.separatorChar, '/');
0752: dispatchEvent(SVNEventFactory
0753: .createUpdateExternalEvent(
0754: (SVNAdminAreaInfo) null,
0755: relativePath));
0756: try {
0757: setEventPathPrefix(relativePath);
0758: doExport(srcURL, targetDir, srcRevision,
0759: srcRevision, eolStyle, force,
0760: recursive);
0761: } catch (SVNException e) {
0762: if (e instanceof SVNCancelException) {
0763: throw e;
0764: }
0765: dispatchEvent(new SVNEvent(e
0766: .getErrorMessage()));
0767: } finally {
0768: setEventPathPrefix(null);
0769: }
0770: }
0771: }
0772: }
0773: } else if (dstKind == SVNNodeKind.FILE) {
0774: String url = repository.getLocation().toString();
0775: if (dstPath.isDirectory()) {
0776: dstPath = new File(dstPath, SVNEncodingUtil
0777: .uriDecode(SVNPathUtil.tail(url)));
0778: }
0779: if (dstPath.exists()) {
0780: if (!force) {
0781: SVNErrorMessage err = SVNErrorMessage.create(
0782: SVNErrorCode.WC_OBSTRUCTED_UPDATE,
0783: "Path ''{0}'' already exists", dstPath);
0784: SVNErrorManager.error(err);
0785: }
0786: } else {
0787: dstPath.getParentFile().mkdirs();
0788: }
0789: Map properties = new HashMap();
0790: OutputStream os = null;
0791: File tmpFile = SVNFileUtil.createUniqueFile(dstPath
0792: .getParentFile(), ".export", ".tmp");
0793: try {
0794: os = SVNFileUtil.openFileForWriting(tmpFile);
0795: try {
0796: repository.getFile("", revNumber, properties,
0797: new SVNCancellableOutputStream(os, this ));
0798: } finally {
0799: SVNFileUtil.closeFile(os);
0800: }
0801: if (force && dstPath.exists()) {
0802: SVNFileUtil.deleteAll(dstPath, this );
0803: }
0804: boolean binary = SVNProperty
0805: .isBinaryMimeType((String) properties
0806: .get(SVNProperty.MIME_TYPE));
0807: Map keywords = SVNTranslator.computeKeywords(
0808: (String) properties.get(SVNProperty.KEYWORDS),
0809: url, (String) properties
0810: .get(SVNProperty.LAST_AUTHOR),
0811: (String) properties
0812: .get(SVNProperty.COMMITTED_DATE),
0813: (String) properties
0814: .get(SVNProperty.COMMITTED_REVISION),
0815: getOptions());
0816: byte[] eols = null;
0817: if (SVNProperty.EOL_STYLE_NATIVE.equals(properties
0818: .get(SVNProperty.EOL_STYLE))) {
0819: eols = SVNTranslator
0820: .getWorkingEOL(eolStyle != null ? eolStyle
0821: : (String) properties
0822: .get(SVNProperty.EOL_STYLE));
0823: } else if (properties
0824: .containsKey(SVNProperty.EOL_STYLE)) {
0825: eols = SVNTranslator
0826: .getWorkingEOL((String) properties
0827: .get(SVNProperty.EOL_STYLE));
0828: }
0829: if (binary) {
0830: eols = null;
0831: keywords = null;
0832: }
0833: SVNTranslator.translate(tmpFile, dstPath, eols,
0834: keywords,
0835: properties.get(SVNProperty.SPECIAL) != null,
0836: true);
0837: } finally {
0838: SVNFileUtil.deleteFile(tmpFile);
0839: }
0840: if (properties.get(SVNProperty.EXECUTABLE) != null) {
0841: SVNFileUtil.setExecutable(dstPath, true);
0842: }
0843: dispatchEvent(SVNEventFactory.createExportAddedEvent(
0844: dstPath.getParentFile(), dstPath, SVNNodeKind.FILE));
0845: } else {
0846: SVNErrorMessage err = SVNErrorMessage.create(
0847: SVNErrorCode.RA_ILLEGAL_URL,
0848: "URL ''{0}'' doesn't exist", repository
0849: .getLocation());
0850: SVNErrorManager.error(err);
0851: }
0852: return revNumber;
0853: }
0854:
0855: /**
0856: * Substitutes the beginning part of a Working Copy's URL with a new one.
0857: *
0858: * <p>
0859: * When a repository root location or a URL schema is changed the old URL of the
0860: * Working Copy which starts with <code>oldURL</code> should be substituted for a
0861: * new URL beginning - <code>newURL</code>.
0862: *
0863: * @param dst a Working Copy item's path
0864: * @param oldURL the old beginning part of the repository's URL that should
0865: * be overwritten
0866: * @param newURL a new beginning part for the repository location that
0867: * will overwrite <code>oldURL</code>
0868: * @param recursive if <span class="javakeyword">true</span> and <code>dst</code> is
0869: * a directory then the entire tree will be relocated, otherwise if
0870: * <span class="javakeyword">false</span> - only <code>dst</code> itself
0871: * @throws SVNException
0872: */
0873: public void doRelocate(File dst, SVNURL oldURL, SVNURL newURL,
0874: boolean recursive) throws SVNException {
0875: SVNWCAccess wcAccess = createWCAccess();
0876: try {
0877: SVNAdminArea adminArea = wcAccess.probeOpen(dst, true,
0878: recursive ? SVNWCAccess.INFINITE_DEPTH : 0);
0879: String name = dst.equals(adminArea.getRoot()) ? adminArea
0880: .getThisDirName() : dst.getName();
0881: String from = oldURL.toString();
0882: String to = newURL.toString();
0883: if (from.endsWith("/")) {
0884: from = from.substring(0, from.length() - 1);
0885: }
0886: if (to.endsWith("/")) {
0887: to = to.substring(0, to.length() - 1);
0888: }
0889: doRelocate(adminArea, name, from, to, recursive,
0890: new HashMap());
0891: } finally {
0892: wcAccess.close();
0893: }
0894: }
0895:
0896: /**
0897: * Canonicalizes all urls in the specified Working Copy.
0898: *
0899: * @param dst a WC path
0900: * @param omitDefaultPort if <span class="javakeyword">true</span> then removes all
0901: * port numbers from urls which equal to default ones, otherwise
0902: * does not
0903: * @param recursive recurses an operation
0904: * @throws SVNException
0905: */
0906: public void doCanonicalizeURLs(File dst, boolean omitDefaultPort,
0907: boolean recursive) throws SVNException {
0908: SVNWCAccess wcAccess = createWCAccess();
0909: try {
0910: SVNAdminAreaInfo adminAreaInfo = wcAccess.openAnchor(dst,
0911: true, recursive ? SVNWCAccess.INFINITE_DEPTH : 0);
0912: SVNAdminArea target = adminAreaInfo.getTarget();
0913: SVNEntry entry = wcAccess.getEntry(dst, false);
0914: String name = target.getThisDirName();
0915: if (entry != null && entry.isFile()) {
0916: name = entry.getName();
0917: }
0918: doCanonicalizeURLs(adminAreaInfo, target, name,
0919: omitDefaultPort, recursive);
0920: if (recursive && !isIgnoreExternals()) {
0921: for (Iterator externals = adminAreaInfo.externals(); externals
0922: .hasNext();) {
0923: SVNExternalInfo info = (SVNExternalInfo) externals
0924: .next();
0925: try {
0926: doCanonicalizeURLs(info.getFile(),
0927: omitDefaultPort, true);
0928: } catch (SVNCancelException e) {
0929: throw e;
0930: } catch (SVNException e) {
0931: getDebugLog().info(e);
0932: }
0933: }
0934: }
0935: } finally {
0936: wcAccess.close();
0937: }
0938: }
0939:
0940: private void doCanonicalizeURLs(SVNAdminAreaInfo adminAreaInfo,
0941: SVNAdminArea adminArea, String name,
0942: boolean omitDefaultPort, boolean recursive)
0943: throws SVNException {
0944: boolean save = false;
0945: checkCancelled();
0946: if (!adminArea.getThisDirName().equals(name)) {
0947: SVNEntry entry = adminArea.getEntry(name, true);
0948: save = canonicalizeEntry(entry, omitDefaultPort);
0949: adminArea.getWCProperties(name).setPropertyValue(
0950: SVNProperty.WC_URL, null);
0951: if (save) {
0952: adminArea.saveEntries(false);
0953: }
0954: return;
0955: }
0956: if (!isIgnoreExternals()) {
0957: String externalsValue = adminArea.getProperties(
0958: adminArea.getThisDirName()).getPropertyValue(
0959: SVNProperty.EXTERNALS);
0960: adminAreaInfo.addExternals(adminArea, externalsValue);
0961: if (externalsValue != null) {
0962: externalsValue = canonicalizeExtenrals(externalsValue,
0963: omitDefaultPort);
0964: adminArea.getProperties(adminArea.getThisDirName())
0965: .setPropertyValue(SVNProperty.EXTERNALS,
0966: externalsValue);
0967: }
0968: }
0969:
0970: SVNEntry rootEntry = adminArea.getEntry(adminArea
0971: .getThisDirName(), true);
0972: save = canonicalizeEntry(rootEntry, omitDefaultPort);
0973: adminArea.getWCProperties(adminArea.getThisDirName())
0974: .setPropertyValue(SVNProperty.WC_URL, null);
0975: // now all child entries that doesn't has repos/url has new values.
0976: for (Iterator ents = adminArea.entries(true); ents.hasNext();) {
0977: SVNEntry entry = (SVNEntry) ents.next();
0978: if (adminArea.getThisDirName().equals(entry.getName())) {
0979: continue;
0980: }
0981: checkCancelled();
0982: if (recursive
0983: && entry.isDirectory()
0984: && (entry.isScheduledForAddition() || !entry
0985: .isDeleted()) && !entry.isAbsent()) {
0986: SVNAdminArea childArea = adminArea.getWCAccess()
0987: .retrieve(adminArea.getFile(entry.getName()));
0988: if (childArea != null) {
0989: doCanonicalizeURLs(adminAreaInfo, childArea, "",
0990: omitDefaultPort, recursive);
0991: }
0992: }
0993: save |= canonicalizeEntry(entry, omitDefaultPort);
0994: SVNVersionedProperties properties = adminArea
0995: .getWCProperties(entry.getName());
0996: if (properties != null) {
0997: properties.setPropertyValue(SVNProperty.WC_URL, null);
0998: }
0999: }
1000: if (save) {
1001: adminArea.saveEntries(true);
1002: }
1003: }
1004:
1005: private static String canonicalizeExtenrals(String externals,
1006: boolean omitDefaultPort) throws SVNException {
1007: if (externals == null) {
1008: return null;
1009: }
1010: StringBuffer canonicalized = new StringBuffer();
1011: for (StringTokenizer lines = new StringTokenizer(externals,
1012: "\r\n", true); lines.hasMoreTokens();) {
1013: String line = lines.nextToken();
1014: if (line.trim().length() == 0
1015: || line.trim().startsWith("#")
1016: || line.indexOf('\r') >= 0
1017: || line.indexOf('\n') >= 0) {
1018: canonicalized.append(line);
1019: continue;
1020: }
1021: String[] tokens = line.split("[ \t]");
1022: int index = tokens.length - 1;
1023: SVNURL url = null;
1024: if (index >= 1) {
1025: try {
1026: url = SVNURL.parseURIEncoded(tokens[index]);
1027: } catch (SVNException e) {
1028: url = null;
1029: }
1030: }
1031: SVNURL canonicalURL = canonicalizeURL(url, omitDefaultPort);
1032: if (canonicalURL == null) {
1033: canonicalized.append(line);
1034: } else {
1035: canonicalized.append(tokens[0]);
1036: canonicalized.append(' ');
1037: if (index == 2) {
1038: canonicalized.append(tokens[1]);
1039: canonicalized.append(' ');
1040: }
1041: canonicalized.append(canonicalURL.toString());
1042: }
1043: }
1044: return canonicalized.toString();
1045: }
1046:
1047: private static boolean canonicalizeEntry(SVNEntry entry,
1048: boolean omitDefaultPort) throws SVNException {
1049: boolean updated = false;
1050: SVNURL root = canonicalizeURL(entry.getRepositoryRootURL(),
1051: omitDefaultPort);
1052: if (root != null) {
1053: updated |= entry.setRepositoryRootURL(root);
1054: }
1055: SVNURL url = canonicalizeURL(entry.getSVNURL(), omitDefaultPort);
1056: if (url != null) {
1057: updated |= entry.setURL(url.toString());
1058: }
1059: SVNURL copyFrom = canonicalizeURL(entry.getCopyFromSVNURL(),
1060: omitDefaultPort);
1061: if (copyFrom != null) {
1062: updated |= entry.setCopyFromURL(copyFrom.toString());
1063: }
1064: return updated;
1065: }
1066:
1067: private static SVNURL canonicalizeURL(SVNURL url,
1068: boolean omitDefaultPort) throws SVNException {
1069: if (url == null || url.getPort() <= 0) {
1070: // no url or file url.
1071: return null;
1072: }
1073: int defaultPort = SVNURL
1074: .getDefaultPortNumber(url.getProtocol());
1075: if (defaultPort <= 0) {
1076: // file or svn+ext URL.
1077: return null;
1078: }
1079: if (omitDefaultPort) {
1080: // remove port if it is same as default.
1081: if (url.hasPort() && url.getPort() == defaultPort) {
1082: return SVNURL.create(url.getProtocol(), url
1083: .getUserInfo(), url.getHost(), -1, url
1084: .getPath(), false);
1085: }
1086: } else if (!url.hasPort()) {
1087: // set port if there is no port set.
1088: return SVNURL.create(url.getProtocol(), url.getUserInfo(),
1089: url.getHost(), url.getPort(), url.getPath(), false);
1090: }
1091: return null;
1092: }
1093:
1094: private void handleExternals(SVNAdminAreaInfo info)
1095: throws SVNException {
1096: for (Iterator externals = info.externals(); externals.hasNext();) {
1097: SVNExternalInfo external = (SVNExternalInfo) externals
1098: .next();
1099: if (external.getOldURL() == null
1100: && external.getNewURL() == null) {
1101: continue;
1102: }
1103: long revNumber = external.getNewRevision();
1104: SVNRevision revision = revNumber >= 0 ? SVNRevision
1105: .create(revNumber) : getExternalRevision(external
1106: .getFile(), external.getNewURL());
1107: setEventPathPrefix(external.getPath());
1108: try {
1109: if (external.getOldURL() == null) {
1110: external.getFile().mkdirs();
1111: dispatchEvent(SVNEventFactory
1112: .createUpdateExternalEvent(info, ""));
1113: doCheckout(external.getNewURL(),
1114: external.getFile(), revision, revision,
1115: true);
1116: } else if (external.getNewURL() == null) {
1117: SVNWCAccess wcAccess = createWCAccess();
1118: SVNAdminArea area = wcAccess.open(external
1119: .getFile(), true,
1120: SVNWCAccess.INFINITE_DEPTH);
1121: SVNException error = null;
1122: try {
1123: area.removeFromRevisionControl(area
1124: .getThisDirName(), true, false);
1125: } catch (SVNException svne) {
1126: error = svne;
1127: }
1128:
1129: if (error == null
1130: || error.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) {
1131: try {
1132: wcAccess.close();
1133: } catch (SVNException svne) {
1134: error = error == null ? svne : error;
1135: }
1136: }
1137:
1138: if (error != null
1139: && error.getErrorMessage().getErrorCode() != SVNErrorCode.WC_LEFT_LOCAL_MOD) {
1140: throw error;
1141: }
1142: } else {
1143: dispatchEvent(SVNEventFactory
1144: .createUpdateExternalEvent(info, ""));
1145: if (!external.getFile().isDirectory()) {
1146: boolean created = external.getFile().mkdirs();
1147: try {
1148: doCheckout(external.getNewURL(), external
1149: .getFile(), revision, revision,
1150: true);
1151: } catch (SVNException e) {
1152: if (created
1153: && e.getErrorMessage()
1154: .getErrorCode() == SVNErrorCode.RA_ILLEGAL_URL) {
1155: SVNFileUtil.deleteAll(external
1156: .getFile(), true);
1157: }
1158: throw e;
1159: }
1160: } else {
1161: File[] children = external.getFile()
1162: .listFiles();
1163: if (children != null && children.length == 0) {
1164: // unversioned empty directory.
1165: try {
1166: doCheckout(external.getNewURL(),
1167: external.getFile(), revision,
1168: revision, true);
1169: } catch (SVNException e) {
1170: throw e;
1171: }
1172: continue;
1173: }
1174: SVNWCAccess wcAccess = createWCAccess();
1175: SVNAdminArea area = wcAccess.open(external
1176: .getFile(), true, 0);
1177: SVNEntry entry = area.getEntry(area
1178: .getThisDirName(), false);
1179: wcAccess.close();
1180: String url = entry.getURL();
1181:
1182: if (entry != null && entry.getURL() != null) {
1183: if (external.getNewURL().toString().equals(
1184: url)) {
1185: doUpdate(external.getFile(), revision,
1186: true);
1187: continue;
1188: } else if (entry.getRepositoryRoot() != null) {
1189: if (!SVNPathUtil.isAncestor(entry
1190: .getRepositoryRoot(), external
1191: .getNewURL().toString())) {
1192: SVNRepository repos = createRepository(
1193: external.getNewURL(), true);
1194: SVNURL reposRoot = repos
1195: .getRepositoryRoot(true);
1196: try {
1197: doRelocate(external.getFile(),
1198: entry.getSVNURL(),
1199: reposRoot, true);
1200: } catch (SVNException svne) {
1201: if (svne.getErrorMessage()
1202: .getErrorCode() == SVNErrorCode.WC_INVALID_RELOCATION
1203: || svne
1204: .getErrorMessage()
1205: .getErrorCode() == SVNErrorCode.CLIENT_INVALID_RELOCATION) {
1206: deleteExternal(external);
1207: external.getFile().mkdirs();
1208: doCheckout(external
1209: .getNewURL(),
1210: external.getFile(),
1211: revision, revision,
1212: true);
1213: continue;
1214: }
1215: throw svne;
1216: }
1217: }
1218: doSwitch(external.getFile(), external
1219: .getNewURL(), revision, true);
1220: continue;
1221: }
1222: }
1223: deleteExternal(external);
1224: external.getFile().mkdirs();
1225: doCheckout(external.getNewURL(), external
1226: .getFile(), revision, revision, true);
1227: }
1228: }
1229: } catch (SVNException th) {
1230: if (th instanceof SVNCancelException) {
1231: throw th;
1232: }
1233: getDebugLog().info(th);
1234: File file = external.getFile();
1235: SVNEvent event = SVNEventFactory
1236: .createSkipEvent(file, file,
1237: SVNEventAction.SKIP,
1238: SVNEventAction.UPDATE_EXTERNAL,
1239: SVNNodeKind.DIR);
1240: event.setErrorMessage(th.getErrorMessage());
1241: dispatchEvent(event);
1242: } finally {
1243: setEventPathPrefix(null);
1244: }
1245: }
1246: }
1247:
1248: protected SVNRevision getExternalRevision(File file, SVNURL newURL) {
1249: if (file == null || newURL == null) {
1250: return SVNRevision.HEAD;
1251: }
1252: return SVNRevision.HEAD;
1253: }
1254:
1255: private void deleteExternal(SVNExternalInfo external)
1256: throws SVNException {
1257: SVNWCAccess wcAccess = createWCAccess();
1258: SVNAdminArea adminArea = wcAccess.open(external.getFile(),
1259: true, SVNWCAccess.INFINITE_DEPTH);
1260: SVNException error = null;
1261: try {
1262: adminArea.removeFromRevisionControl(adminArea
1263: .getThisDirName(), true, false);
1264: } catch (SVNException svne) {
1265: getDebugLog().info(svne);
1266: error = svne;
1267: }
1268:
1269: if (error == null
1270: || error.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) {
1271: wcAccess.close();
1272: }
1273:
1274: if (error != null
1275: && error.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) {
1276: external.getFile().getParentFile().mkdirs();
1277: File newLocation = SVNFileUtil.createUniqueFile(external
1278: .getFile().getParentFile(), external.getFile()
1279: .getName(), ".OLD");
1280: SVNFileUtil.rename(external.getFile(), newLocation);
1281: } else if (error != null) {
1282: throw error;
1283: }
1284: }
1285:
1286: private Map validateRelocateTargetURL(SVNURL targetURL,
1287: String expectedUUID, Map validatedURLs, boolean isRoot)
1288: throws SVNException {
1289: if (validatedURLs == null) {
1290: return null;
1291: }
1292:
1293: for (Iterator targetURLs = validatedURLs.keySet().iterator(); targetURLs
1294: .hasNext();) {
1295: SVNURL validatedURL = (SVNURL) targetURLs.next();
1296: if (targetURL.toString()
1297: .startsWith(validatedURL.toString())) {
1298: if (isRoot && !targetURL.equals(validatedURL)) {
1299: SVNErrorMessage err = SVNErrorMessage
1300: .create(
1301: SVNErrorCode.CLIENT_INVALID_RELOCATION,
1302: "''{0}'' is not the root of the repository",
1303: targetURL);
1304: SVNErrorManager.error(err);
1305: }
1306: String validatedUUID = (String) validatedURLs
1307: .get(validatedURL);
1308: if (expectedUUID != null
1309: && !expectedUUID.equals(validatedUUID)) {
1310: SVNErrorMessage err = SVNErrorMessage
1311: .create(
1312: SVNErrorCode.CLIENT_INVALID_RELOCATION,
1313: "The repository at ''{0}'' has uuid ''{1}'', but the WC has ''{2}''",
1314: new Object[] { validatedURL,
1315: expectedUUID, validatedUUID });
1316: SVNErrorManager.error(err);
1317: }
1318: return validatedURLs;
1319: }
1320: }
1321: SVNRepository repos = createRepository(targetURL, false);
1322: try {
1323: SVNURL actualRoot = repos.getRepositoryRoot(true);
1324: if (isRoot && !targetURL.equals(actualRoot)) {
1325: SVNErrorMessage err = SVNErrorMessage.create(
1326: SVNErrorCode.CLIENT_INVALID_RELOCATION,
1327: "''{0}'' is not the root of the repository",
1328: targetURL);
1329: SVNErrorManager.error(err);
1330: }
1331:
1332: String actualUUID = repos.getRepositoryUUID(true);
1333: if (expectedUUID != null
1334: && !expectedUUID.equals(actualUUID)) {
1335: SVNErrorMessage err = SVNErrorMessage
1336: .create(
1337: SVNErrorCode.CLIENT_INVALID_RELOCATION,
1338: "The repository at ''{0}'' has uuid ''{1}'', but the WC has ''{2}''",
1339: new Object[] { targetURL, expectedUUID,
1340: actualUUID });
1341: SVNErrorManager.error(err);
1342: }
1343: validatedURLs.put(targetURL, actualUUID);
1344: } finally {
1345: repos.closeSession();
1346: }
1347: return validatedURLs;
1348: }
1349:
1350: private Map relocateEntry(SVNEntry entry, String from, String to,
1351: Map validatedURLs) throws SVNException {
1352: if (entry.getRepositoryRoot() != null) {
1353: // that is what i do not understand :)
1354: String repos = entry.getRepositoryRoot();
1355: if (from.length() > repos.length()) {
1356: String fromPath = from.substring(repos.length());
1357: if (!to.endsWith(fromPath)) {
1358: SVNErrorMessage err = SVNErrorMessage
1359: .create(SVNErrorCode.WC_INVALID_RELOCATION,
1360: "Relocate can only change the repository part of an URL");
1361: SVNErrorManager.error(err);
1362: }
1363: from = repos;
1364: to = to.substring(0, to.length() - fromPath.length());
1365: }
1366: if (repos.startsWith(from)) {
1367: entry.setRepositoryRoot(to
1368: + repos.substring(from.length()));
1369: validatedURLs = validateRelocateTargetURL(entry
1370: .getRepositoryRootURL(), entry.getUUID(),
1371: validatedURLs, true);
1372: }
1373: }
1374: if (entry.getURL() != null && entry.getURL().startsWith(from)) {
1375: entry.setURL(to + entry.getURL().substring(from.length()));
1376: if (entry.getUUID() != null && validatedURLs != null) {
1377: validatedURLs = validateRelocateTargetURL(entry
1378: .getSVNURL(), entry.getUUID(), validatedURLs,
1379: false);
1380: }
1381: }
1382: if (entry.getCopyFromURL() != null
1383: && entry.getCopyFromURL().startsWith(from)) {
1384: entry.setCopyFromURL(to
1385: + entry.getCopyFromURL().substring(from.length()));
1386: if (entry.getUUID() != null && validatedURLs != null) {
1387: validatedURLs = validateRelocateTargetURL(entry
1388: .getCopyFromSVNURL(), entry.getUUID(),
1389: validatedURLs, false);
1390: }
1391: }
1392: return validatedURLs;
1393: }
1394:
1395: private Map doRelocate(SVNAdminArea adminArea, String name,
1396: String from, String to, boolean recursive, Map validatedURLs)
1397: throws SVNException {
1398: SVNEntry entry = adminArea.getEntry(name, true);
1399: if (entry == null) {
1400: SVNErrorMessage err = SVNErrorMessage
1401: .create(SVNErrorCode.ENTRY_NOT_FOUND);
1402: SVNErrorManager.error(err);
1403: }
1404:
1405: if (entry.isFile()) {
1406: relocateEntry(entry, from, to, validatedURLs);
1407: SVNPropertiesManager.deleteWCProperties(adminArea, name,
1408: false);
1409: adminArea.saveEntries(false);
1410: return validatedURLs;
1411: }
1412:
1413: validatedURLs = relocateEntry(entry, from, to, validatedURLs);
1414: SVNWCAccess wcAccess = adminArea.getWCAccess();
1415: for (Iterator entries = adminArea.entries(true); entries
1416: .hasNext();) {
1417: SVNEntry childEntry = (SVNEntry) entries.next();
1418: if (adminArea.getThisDirName().equals(childEntry.getName())) {
1419: continue;
1420: }
1421: if (recursive
1422: && childEntry.isDirectory()
1423: && (childEntry.isScheduledForAddition() || !childEntry
1424: .isDeleted()) && !childEntry.isAbsent()) {
1425: File childDir = adminArea.getFile(childEntry.getName());
1426: if (wcAccess.isMissing(childDir)) {
1427: continue;
1428: }
1429: SVNAdminArea childArea = wcAccess.retrieve(childDir);
1430: validatedURLs = doRelocate(childArea, childArea
1431: .getThisDirName(), from, to, recursive,
1432: validatedURLs);
1433: }
1434: validatedURLs = relocateEntry(childEntry, from, to,
1435: validatedURLs);
1436: SVNPropertiesManager.deleteWCProperties(adminArea,
1437: childEntry.getName(), false);
1438: }
1439: SVNPropertiesManager.deleteWCProperties(adminArea, "", false);
1440: adminArea.saveEntries(false);
1441: return validatedURLs;
1442: }
1443: }
|