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.admin;
0013:
0014: import java.io.ByteArrayOutputStream;
0015: import java.io.File;
0016: import java.io.IOException;
0017: import java.io.InputStream;
0018: import java.io.OutputStream;
0019: import java.net.InetAddress;
0020: import java.net.UnknownHostException;
0021: import java.nio.charset.Charset;
0022: import java.nio.charset.CharsetDecoder;
0023: import java.util.HashMap;
0024: import java.util.Iterator;
0025: import java.util.Map;
0026:
0027: import org.tmatesoft.svn.core.ISVNLogEntryHandler;
0028: import org.tmatesoft.svn.core.SVNErrorCode;
0029: import org.tmatesoft.svn.core.SVNErrorMessage;
0030: import org.tmatesoft.svn.core.SVNException;
0031: import org.tmatesoft.svn.core.SVNLock;
0032: import org.tmatesoft.svn.core.SVNNodeKind;
0033: import org.tmatesoft.svn.core.SVNProperty;
0034: import org.tmatesoft.svn.core.SVNRevisionProperty;
0035: import org.tmatesoft.svn.core.SVNURL;
0036: import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
0037: import org.tmatesoft.svn.core.internal.io.fs.FSCommitter;
0038: import org.tmatesoft.svn.core.internal.io.fs.FSFS;
0039: import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryUtil;
0040: import org.tmatesoft.svn.core.internal.io.fs.FSRevisionRoot;
0041: import org.tmatesoft.svn.core.internal.util.SVNDate;
0042: import org.tmatesoft.svn.core.internal.util.SVNUUIDGenerator;
0043: import org.tmatesoft.svn.core.internal.wc.DefaultLoadHandler;
0044: import org.tmatesoft.svn.core.internal.wc.ISVNLoadHandler;
0045: import org.tmatesoft.svn.core.internal.wc.SVNAdminHelper;
0046: import org.tmatesoft.svn.core.internal.wc.SVNCancellableEditor;
0047: import org.tmatesoft.svn.core.internal.wc.SVNDumpEditor;
0048: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
0049: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
0050: import org.tmatesoft.svn.core.internal.wc.SVNSynchronizeEditor;
0051: import org.tmatesoft.svn.core.io.ISVNEditor;
0052: import org.tmatesoft.svn.core.io.ISVNLockHandler;
0053: import org.tmatesoft.svn.core.io.SVNRepository;
0054: import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
0055: import org.tmatesoft.svn.core.replicator.SVNRepositoryReplicator;
0056: import org.tmatesoft.svn.core.wc.ISVNEventHandler;
0057: import org.tmatesoft.svn.core.wc.ISVNOptions;
0058: import org.tmatesoft.svn.core.wc.ISVNRepositoryPool;
0059: import org.tmatesoft.svn.core.wc.SVNBasicClient;
0060: import org.tmatesoft.svn.core.wc.SVNRevision;
0061: import org.tmatesoft.svn.util.SVNDebugLog;
0062:
0063: /**
0064: * The <b>SVNAdminClient</b> class provides methods that brings repository-side functionality
0065: * and repository synchronizing features.
0066: *
0067: * <p>
0068: * Repository administrative methods are analogues of the corresponding commands of the native
0069: * Subversion 'svnadmin' utility, while repository synchronizing methods are the ones for the
0070: * 'svnsync' utility.
0071: *
0072: * <p>
0073: * Here's a list of the <b>SVNAdminClient</b>'s methods
0074: * matched against corresponing commands of the Subversion svnsync and svnadmin command-line utilities:
0075: *
0076: * <table cellpadding="3" cellspacing="1" border="0" width="40%" bgcolor="#999933">
0077: * <tr bgcolor="#ADB8D9" align="left">
0078: * <td><b>SVNKit</b></td>
0079: * <td><b>Subversion</b></td>
0080: * </tr>
0081: * <tr bgcolor="#EAEAEA" align="left">
0082: * <td>doInitialize()</td><td>'svnsync initialize'</td>
0083: * </tr>
0084: * <tr bgcolor="#EAEAEA" align="left">
0085: * <td>doSynchronize()</td><td>'svnsync synchronize'</td>
0086: * </tr>
0087: * <tr bgcolor="#EAEAEA" align="left">
0088: * <td>doCopyRevisionProperties()</td><td>'svnsync copy-revprops'</td>
0089: * </tr>
0090: * <tr bgcolor="#EAEAEA" align="left">
0091: * <td>doDump()</td><td>'svnadmin dump'</td>
0092: * </tr>
0093: * <tr bgcolor="#EAEAEA" align="left">
0094: * <td>doListTransactions()</td><td>'svnadmin lstxns'</td>
0095: * </tr>
0096: * <tr bgcolor="#EAEAEA" align="left">
0097: * <td>doLoad()</td><td>'svnadmin load'</td>
0098: * </tr>
0099: * <tr bgcolor="#EAEAEA" align="left">
0100: * <td>doRemoveTransactions()</td><td>'svnadmin rmtxns'</td>
0101: * </tr>
0102: * <tr bgcolor="#EAEAEA" align="left">
0103: * <td>doVerify()</td><td>'svnadmin verify'</td>
0104: * </tr>
0105: * </table>
0106: *
0107: * @version 1.1.1
0108: * @author TMate Software Ltd.
0109: * @since 1.1.0
0110: */
0111: public class SVNAdminClient extends SVNBasicClient {
0112: private ISVNLogEntryHandler mySyncHandler;
0113: private ISVNLoadHandler myLoadHandler;
0114: private ISVNAdminEventHandler myEventHandler;
0115:
0116: /**
0117: * Creates a new admin client.
0118: *
0119: * @param authManager an auth manager
0120: * @param options an options driver
0121: */
0122: public SVNAdminClient(ISVNAuthenticationManager authManager,
0123: ISVNOptions options) {
0124: super (authManager, options);
0125: }
0126:
0127: /**
0128: * Creates a new admin client.
0129: *
0130: * @param repositoryPool a repository pool
0131: * @param options an options driver
0132: */
0133: public SVNAdminClient(ISVNRepositoryPool repositoryPool,
0134: ISVNOptions options) {
0135: super (repositoryPool, options);
0136: }
0137:
0138: /**
0139: * Sets a replication handler that will receive a log entry object
0140: * per each replayed revision.
0141: *
0142: * <p>
0143: * Log entries dispatched to the handler may not contain changed paths and
0144: * committed log message until this features are implemented in future releases.
0145: *
0146: * @param handler a replay handler
0147: */
0148: public void setReplayHandler(ISVNLogEntryHandler handler) {
0149: mySyncHandler = handler;
0150: }
0151:
0152: /**
0153: * Sets an event handler for this object.
0154: * {@link ISVNAdminEventHandler} should be provided to <b>SVNAdminClent</b>
0155: * via this method also.
0156: *
0157: * @param handler an event handler
0158: */
0159: public void setEventHandler(ISVNEventHandler handler) {
0160: super .setEventHandler(handler);
0161: if (handler instanceof ISVNAdminEventHandler) {
0162: myEventHandler = (ISVNAdminEventHandler) handler;
0163: }
0164: }
0165:
0166: /**
0167: * Creates an FSFS-type repository.
0168: *
0169: * This implementation uses {@link org.tmatesoft.svn.core.io.SVNRepositoryFactory#createLocalRepository(File, String, boolean, boolean)}}.
0170: * <p>
0171: * If <code>uuid</code> is <span class="javakeyword">null</span> a new uuid will be generated, otherwise
0172: * the specified will be used.
0173: *
0174: * <p>
0175: * If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>, an empty
0176: * pre-revprop-change hook will be placed into the repository /hooks subdir. This enables changes to
0177: * revision properties of the newly created repository.
0178: *
0179: * <p>
0180: * If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
0181: * exists, deletes that path and creates a repository in its place.
0182: *
0183: * @param path a repository root dir path
0184: * @param uuid a repository uuid
0185: * @param enableRevisionProperties enables/disables changes to revision properties
0186: * @param force forces operation to run
0187: * @return a local URL (file:///) of a newly created repository
0188: * @throws SVNException
0189: * @see #doCreateRepository(File, String, boolean, boolean, boolean)
0190: * @since 1.1.0
0191: */
0192: public SVNURL doCreateRepository(File path, String uuid,
0193: boolean enableRevisionProperties, boolean force)
0194: throws SVNException {
0195: return SVNRepositoryFactory.createLocalRepository(path, uuid,
0196: enableRevisionProperties, force);
0197: }
0198:
0199: /**
0200: * Creates an FSFS-type repository.
0201: *
0202: * This implementation uses {@link org.tmatesoft.svn.core.io.SVNRepositoryFactory#createLocalRepository(File, String, boolean, boolean)}}.
0203: * <p>
0204: * If <code>uuid</code> is <span class="javakeyword">null</span> a new uuid will be generated, otherwise
0205: * the specified will be used.
0206: *
0207: * <p>
0208: * If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>, an empty
0209: * pre-revprop-change hook will be placed into the repository /hooks subdir. This enables changes to
0210: * revision properties of the newly created repository.
0211: *
0212: * <p>
0213: * If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
0214: * exists, deletes that path and creates a repository in its place.
0215: *
0216: * <p>
0217: * Set <code>pre14Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
0218: * to be compatible with pre-1.4 servers.
0219: *
0220: * @param path a repository root dir path
0221: * @param uuid a repository uuid
0222: * @param enableRevisionProperties enables/disables changes to revision properties
0223: * @param force forces operation to run
0224: * @param pre14Compatible <span class="javakeyword">true</span> to
0225: * create a repository with pre-1.4 format
0226: * @return a local URL (file:///) of a newly created repository
0227: * @throws SVNException
0228: * @since 1.1.1
0229: */
0230: public SVNURL doCreateRepository(File path, String uuid,
0231: boolean enableRevisionProperties, boolean force,
0232: boolean pre14Compatible) throws SVNException {
0233: return SVNRepositoryFactory.createLocalRepository(path, uuid,
0234: enableRevisionProperties, force, pre14Compatible);
0235: }
0236:
0237: /**
0238: * Copies revision properties from the source repository that the destination one is synchronized with
0239: * to the given revision of the destination repository itself.
0240: *
0241: * <p>
0242: * This method is equivalent to the command 'copy-revprops' of the native Subversion <i>svnsync</i> utility.
0243: * Note that the destination repository given as <code>toURL</code> must be synchronized with a source
0244: * repository. Please, see {@link #doInitialize(SVNURL, SVNURL)}} how to initialize such a synchronization.
0245: *
0246: * @param toURL a url to the destination repository which must be synchronized
0247: * with another repository
0248: * @param revision a particular revision of the source repository to copy revision properties
0249: * from
0250: * @throws SVNException
0251: * @since 1.1, new in Subversion 1.4
0252: */
0253: public void doCopyRevisionProperties(SVNURL toURL, long revision)
0254: throws SVNException {
0255: SVNRepository toRepos = createRepository(toURL, true);
0256: checkIfRepositoryIsAtRoot(toRepos, toURL);
0257:
0258: SVNException error = null;
0259: SVNException error2 = null;
0260: lock(toRepos);
0261: try {
0262: SessionInfo info = openSourceRepository(toRepos);
0263: if (revision > info.myLastMergedRevision) {
0264: SVNErrorMessage err = SVNErrorMessage
0265: .create(SVNErrorCode.IO_ERROR,
0266: "Cannot copy revprops for a revision that has not been synchronized yet");
0267: SVNErrorManager.error(err);
0268: }
0269: copyRevisionProperties(info.myRepository, toRepos,
0270: revision, false);
0271: } catch (SVNException svne) {
0272: error = svne;
0273: } finally {
0274: try {
0275: unlock(toRepos);
0276: } catch (SVNException svne) {
0277: error2 = svne;
0278: }
0279: }
0280:
0281: if (error != null) {
0282: throw error;
0283: } else if (error2 != null) {
0284: throw error2;
0285: }
0286: }
0287:
0288: /**
0289: * Initializes synchronization between source and target repositories.
0290: *
0291: * <p>
0292: * This method is equivalent to the command 'initialize' ('init') of the native Subversion <i>svnsync</i>
0293: * utility. Initialization places information of a source repository to a destination one (setting special
0294: * revision properties in revision 0) as well as copies all revision props from revision 0 of the source
0295: * repository to revision 0 of the destination one.
0296: *
0297: * @param fromURL a source repository url
0298: * @param toURL a destination repository url
0299: * @throws SVNException
0300: * @since 1.1, new in Subversion 1.4
0301: */
0302: public void doInitialize(SVNURL fromURL, SVNURL toURL)
0303: throws SVNException {
0304: SVNRepository toRepos = createRepository(toURL, true);
0305: checkIfRepositoryIsAtRoot(toRepos, toURL);
0306:
0307: SVNException error = null;
0308: SVNException error2 = null;
0309: lock(toRepos);
0310: try {
0311: long latestRevision = toRepos.getLatestRevision();
0312: if (latestRevision != 0) {
0313: SVNErrorMessage err = SVNErrorMessage
0314: .create(SVNErrorCode.IO_ERROR,
0315: "Cannot initialize a repository with content in it");
0316: SVNErrorManager.error(err);
0317: }
0318:
0319: String fromURLProp = toRepos.getRevisionPropertyValue(0,
0320: SVNRevisionProperty.FROM_URL);
0321: if (fromURLProp != null) {
0322: SVNErrorMessage err = SVNErrorMessage
0323: .create(
0324: SVNErrorCode.IO_ERROR,
0325: "Destination repository is already synchronizing from ''{0}''",
0326: fromURLProp);
0327: SVNErrorManager.error(err);
0328: }
0329:
0330: // TODO close session.
0331: SVNRepository fromRepos = createRepository(fromURL, false);
0332: checkIfRepositoryIsAtRoot(fromRepos, fromURL);
0333:
0334: toRepos.setRevisionPropertyValue(0,
0335: SVNRevisionProperty.FROM_URL, fromURL
0336: .toDecodedString());
0337: String uuid = fromRepos.getRepositoryUUID(true);
0338: toRepos.setRevisionPropertyValue(0,
0339: SVNRevisionProperty.FROM_UUID, uuid);
0340: toRepos.setRevisionPropertyValue(0,
0341: SVNRevisionProperty.LAST_MERGED_REVISION, "0");
0342:
0343: copyRevisionProperties(fromRepos, toRepos, 0, false);
0344: } catch (SVNException svne) {
0345: error = svne;
0346: } finally {
0347: try {
0348: unlock(toRepos);
0349: } catch (SVNException svne) {
0350: error2 = svne;
0351: }
0352: }
0353:
0354: if (error != null) {
0355: throw error;
0356: } else if (error2 != null) {
0357: throw error2;
0358: }
0359: }
0360:
0361: /**
0362: * Completely synchronizes two repositories.
0363: *
0364: * <p>
0365: * This method initializes the destination repository and then copies all revision
0366: * changes (including revision properties)
0367: * from the given source repository to the destination one. First it
0368: * tries to use synchronization features similar to the native Subversion
0369: * 'svnsync' capabilities. But if a server does not support
0370: * <code>replay</code> functionality, SVNKit uses its own repository
0371: * replication feature (see {@link org.tmatesoft.svn.core.replicator.SVNRepositoryReplicator}})
0372: *
0373: * @param fromURL a url of a repository to copy from
0374: * @param toURL a destination repository url
0375: * @throws SVNException
0376: * @since 1.1
0377: */
0378: public void doCompleteSynchronize(SVNURL fromURL, SVNURL toURL)
0379: throws SVNException {
0380: try {
0381: doInitialize(fromURL, toURL);
0382: doSynchronize(toURL);
0383: return;
0384: } catch (SVNException svne) {
0385: if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.RA_NOT_IMPLEMENTED) {
0386: throw svne;
0387: }
0388: }
0389:
0390: SVNRepositoryReplicator replicator = SVNRepositoryReplicator
0391: .newInstance();
0392: SVNRepository fromRepos = createRepository(fromURL, true);
0393: // TODO close session
0394: SVNRepository toRepos = createRepository(toURL, false);
0395: replicator.replicateRepository(fromRepos, toRepos, 1, -1);
0396: }
0397:
0398: /**
0399: * Synchronizes the repository at the given url.
0400: *
0401: * <p>
0402: * Synchronization means copying revision changes and revision properties from the source
0403: * repository (that the destination one is synchronized with) to the destination one starting at
0404: * the last merged revision. This method is equivalent to the command 'synchronize' ('sync') of
0405: * the native Subversion <i>svnsync</i> utility.
0406: *
0407: * @param toURL a destination repository url
0408: * @throws SVNException
0409: * @since 1.1, new in Subversion 1.4
0410: */
0411: public void doSynchronize(SVNURL toURL) throws SVNException {
0412: SVNRepository toRepos = createRepository(toURL, true);
0413: checkIfRepositoryIsAtRoot(toRepos, toURL);
0414:
0415: SVNException error = null;
0416: SVNException error2 = null;
0417:
0418: lock(toRepos);
0419: try {
0420: SessionInfo info = openSourceRepository(toRepos);
0421: SVNRepository fromRepos = info.myRepository;
0422: long lastMergedRevision = info.myLastMergedRevision;
0423: String currentlyCopying = toRepos.getRevisionPropertyValue(
0424: 0, SVNRevisionProperty.CURRENTLY_COPYING);
0425: long toLatestRevision = toRepos.getLatestRevision();
0426:
0427: if (currentlyCopying != null) {
0428: long copyingRev = Long.parseLong(currentlyCopying);
0429: if (copyingRev < lastMergedRevision
0430: || copyingRev > lastMergedRevision + 1
0431: || (toLatestRevision != lastMergedRevision && toLatestRevision != copyingRev)) {
0432: SVNErrorMessage err = SVNErrorMessage
0433: .create(
0434: SVNErrorCode.IO_ERROR,
0435: "Revision being currently copied ({0,number,integer}), last merged revision ({1,number,integer}), and destination HEAD ({2,number,integer}) are inconsistent; have you committed to the destination without using svnsync?",
0436: new Long[] {
0437: new Long(copyingRev),
0438: new Long(lastMergedRevision),
0439: new Long(toLatestRevision) });
0440: SVNErrorManager.error(err);
0441: } else if (copyingRev == toLatestRevision) {
0442: if (copyingRev > lastMergedRevision) {
0443: copyRevisionProperties(fromRepos, toRepos,
0444: toLatestRevision, true);
0445: lastMergedRevision = copyingRev;
0446: }
0447: toRepos.setRevisionPropertyValue(0,
0448: SVNRevisionProperty.LAST_MERGED_REVISION,
0449: SVNProperty.toString(lastMergedRevision));
0450: toRepos
0451: .setRevisionPropertyValue(
0452: 0,
0453: SVNRevisionProperty.CURRENTLY_COPYING,
0454: null);
0455: }
0456: } else {
0457: if (toLatestRevision != lastMergedRevision) {
0458: SVNErrorMessage err = SVNErrorMessage
0459: .create(
0460: SVNErrorCode.IO_ERROR,
0461: "Destination HEAD ({0,number,integer}) is not the last merged revision ({1,number,integer}); have you committed to the destination without using svnsync?",
0462: new Long[] {
0463: new Long(toLatestRevision),
0464: new Long(lastMergedRevision) });
0465: SVNErrorManager.error(err);
0466: }
0467: }
0468:
0469: long fromLatestRevision = fromRepos.getLatestRevision();
0470: if (fromLatestRevision < lastMergedRevision) {
0471: return;
0472: }
0473:
0474: for (long currentRev = lastMergedRevision + 1; currentRev <= fromLatestRevision; currentRev++) {
0475: toRepos.setRevisionPropertyValue(0,
0476: SVNRevisionProperty.CURRENTLY_COPYING,
0477: SVNProperty.toString(currentRev));
0478: SVNSynchronizeEditor syncEditor = new SVNSynchronizeEditor(
0479: toRepos, mySyncHandler, currentRev - 1);
0480: ISVNEditor cancellableEditor = SVNCancellableEditor
0481: .newInstance(syncEditor, this , getDebugLog());
0482: try {
0483: fromRepos.replay(0, currentRev, true,
0484: cancellableEditor);
0485: } catch (SVNException e) {
0486: try {
0487: cancellableEditor.abortEdit();
0488: } catch (SVNException abortError) {
0489: }
0490: throw e;
0491: }
0492: cancellableEditor.closeEdit();
0493: if (syncEditor.getCommitInfo().getNewRevision() != currentRev) {
0494: SVNErrorMessage err = SVNErrorMessage
0495: .create(
0496: SVNErrorCode.IO_ERROR,
0497: "Commit created rev {0,number,integer} but should have created {1,number,integer}",
0498: new Long[] {
0499: new Long(syncEditor
0500: .getCommitInfo()
0501: .getNewRevision()),
0502: new Long(currentRev) });
0503: SVNErrorManager.error(err);
0504: }
0505: copyRevisionProperties(fromRepos, toRepos, currentRev,
0506: true);
0507: toRepos.setRevisionPropertyValue(0,
0508: SVNRevisionProperty.LAST_MERGED_REVISION,
0509: SVNProperty.toString(currentRev));
0510: toRepos.setRevisionPropertyValue(0,
0511: SVNRevisionProperty.CURRENTLY_COPYING, null);
0512: }
0513: } catch (SVNException svne) {
0514: error = svne;
0515: } finally {
0516: try {
0517: unlock(toRepos);
0518: } catch (SVNException svne) {
0519: error2 = svne;
0520: }
0521: }
0522:
0523: if (error != null) {
0524: throw error;
0525: } else if (error2 != null) {
0526: throw error2;
0527: }
0528: }
0529:
0530: public void doListLocks(File repositoryRoot) throws SVNException {
0531: FSFS fsfs = SVNAdminHelper.openRepository(repositoryRoot);
0532: File digestFile = fsfs.getDigestFileFromRepositoryPath("/");
0533: ISVNLockHandler handler = new ISVNLockHandler() {
0534: public void handleLock(String path, SVNLock lock,
0535: SVNErrorMessage error) throws SVNException {
0536: checkCancelled();
0537: if (myEventHandler != null) {
0538: SVNAdminEvent event = new SVNAdminEvent(
0539: SVNAdminEventAction.LOCK_LISTED, lock,
0540: error, null);
0541: myEventHandler.handleAdminEvent(event,
0542: ISVNEventHandler.UNKNOWN);
0543: }
0544:
0545: }
0546:
0547: public void handleUnlock(String path, SVNLock lock,
0548: SVNErrorMessage error) throws SVNException {
0549: }
0550: };
0551: fsfs.walkDigestFiles(digestFile, handler, false);
0552: }
0553:
0554: public void doRemoveLocks(File repositoryRoot, String[] paths)
0555: throws SVNException {
0556: if (paths == null) {
0557: return;
0558: }
0559:
0560: FSFS fsfs = SVNAdminHelper.openRepository(repositoryRoot);
0561: for (int i = 0; i < paths.length; i++) {
0562: String path = paths[i];
0563: if (path == null) {
0564: continue;
0565: }
0566: checkCancelled();
0567:
0568: SVNLock lock = null;
0569: try {
0570: lock = fsfs.getLockHelper(path, false);
0571: if (lock == null) {
0572: if (myEventHandler != null) {
0573: SVNAdminEvent event = new SVNAdminEvent(
0574: SVNAdminEventAction.NOT_LOCKED, lock,
0575: null, "Path '" + path
0576: + "' isn't locked.");
0577: myEventHandler.handleAdminEvent(event,
0578: ISVNEventHandler.UNKNOWN);
0579: }
0580: continue;
0581: }
0582:
0583: fsfs.unlockPath(path, lock.getID(), null, true, false);
0584: if (myEventHandler != null) {
0585: SVNAdminEvent event = new SVNAdminEvent(
0586: SVNAdminEventAction.UNLOCKED, lock, null,
0587: "Removed lock on '" + path + "'.");
0588: myEventHandler.handleAdminEvent(event,
0589: ISVNEventHandler.UNKNOWN);
0590: }
0591: } catch (SVNException svne) {
0592: if (myEventHandler != null) {
0593: SVNAdminEvent event = new SVNAdminEvent(
0594: SVNAdminEventAction.UNLOCK_FAILED, lock,
0595: svne.getErrorMessage(), "svnadmin: "
0596: + svne.getErrorMessage()
0597: .getFullMessage());
0598: myEventHandler.handleAdminEvent(event,
0599: ISVNEventHandler.UNKNOWN);
0600: }
0601: }
0602: }
0603: }
0604:
0605: /**
0606: * Lists all uncommitted transactions.
0607: * On each uncommetted transaction found this method fires an {@link SVNAdminEvent}
0608: * with action set to {@link SVNAdminEventAction#TRANSACTION_LISTED} to the registered
0609: * {@link ISVNAdminEventHandler} (if any). To register your <b>ISVNAdminEventHandler</b>
0610: * pass it to {@link #setEventHandler(ISVNEventHandler)}. For this operation the following
0611: * information can be retrieved out of {@link SVNAdminEvent}:
0612: * <ul>
0613: * <li>transaction name - use {@link SVNAdminEvent#getTxnName() SVNAdminEvent.getTxnName()} to get it</li>
0614: * <li>transaction directory - use {@link SVNAdminEvent#getTxnDir() SVNAdminEvent.getTxnDir()} to get it</li>
0615: * </ul>
0616: *
0617: * @param repositoryRoot a repository root directory path
0618: * @throws SVNException
0619: * @since 1.1.1
0620: */
0621: public void doListTransactions(File repositoryRoot)
0622: throws SVNException {
0623: FSFS fsfs = SVNAdminHelper.openRepository(repositoryRoot);
0624: Map txns = fsfs.listTransactions();
0625:
0626: for (Iterator names = txns.keySet().iterator(); names.hasNext();) {
0627: String txnName = (String) names.next();
0628: File txnDir = (File) txns.get(txnName);
0629: SVNDebugLog.getDefaultLog().info(txnName + "\n");
0630: if (myEventHandler != null) {
0631: SVNAdminEvent event = new SVNAdminEvent(txnName,
0632: txnDir, SVNAdminEventAction.TRANSACTION_LISTED);
0633: myEventHandler.handleAdminEvent(event,
0634: ISVNEventHandler.UNKNOWN);
0635: }
0636: }
0637: }
0638:
0639: /**
0640: * Removes the specified outstanding transactions from a repository.
0641: * On each transaction removed this method fires an {@link SVNAdminEvent}
0642: * with action set to {@link SVNAdminEventAction#TRANSACTION_REMOVED} to the registered
0643: * {@link ISVNAdminEventHandler} (if any). To register your <b>ISVNAdminEventHandler</b>
0644: * pass it to {@link #setEventHandler(ISVNEventHandler)}. For this operation the following
0645: * information can be retrieved out of {@link SVNAdminEvent}:
0646: * <ul>
0647: * <li>transaction name - use {@link SVNAdminEvent#getTxnName() SVNAdminEvent.getTxnName()} to get it</li>
0648: * <li>transaction directory - use {@link SVNAdminEvent#getTxnDir() SVNAdminEvent.getTxnDir()} to get it</li>
0649: * </ul>
0650: *
0651: * @param repositoryRoot a repository root directory path
0652: * @param transactions an array with transaction names
0653: * @throws SVNException
0654: * @since 1.1.1
0655: */
0656: public void doRemoveTransactions(File repositoryRoot,
0657: String[] transactions) throws SVNException {
0658: if (transactions == null) {
0659: return;
0660: }
0661:
0662: FSFS fsfs = SVNAdminHelper.openRepository(repositoryRoot);
0663: for (int i = 0; i < transactions.length; i++) {
0664: String txnName = transactions[i];
0665: fsfs.openTxn(txnName);
0666: FSCommitter.purgeTxn(fsfs, txnName);
0667: SVNDebugLog.getDefaultLog().info(
0668: "Transaction '" + txnName + "' removed.\n");
0669: if (myEventHandler != null) {
0670: SVNAdminEvent event = new SVNAdminEvent(txnName, fsfs
0671: .getTransactionDir(txnName),
0672: SVNAdminEventAction.TRANSACTION_REMOVED);
0673: myEventHandler.handleAdminEvent(event,
0674: ISVNEventHandler.UNKNOWN);
0675: }
0676: }
0677: }
0678:
0679: /**
0680: * Verifies the data stored in the repository. This method uses the dump implementation
0681: * (non incremental, beginning with revision 0, ending at the latest one)
0682: * passing a dummy output stream to it. This allows to check the integrity of the
0683: * repository data.
0684: *
0685: * @param repositoryRoot a repository root directory path
0686: * @throws SVNException verification failed - a repository may be corrupted
0687: * @since 1.1.1
0688: */
0689: public void doVerify(File repositoryRoot) throws SVNException {
0690: doVerify(repositoryRoot, SVNRevision.create(0),
0691: SVNRevision.HEAD);
0692: }
0693:
0694: public void doVerify(File repositoryRoot,
0695: SVNRevision startRevision, SVNRevision endRevision)
0696: throws SVNException {
0697: FSFS fsfs = SVNAdminHelper.openRepository(repositoryRoot);
0698: long startRev = startRevision.getNumber();
0699: long endRev = endRevision.getNumber();
0700: if (startRev < 0) {
0701: startRev = 0;
0702: }
0703: if (endRev < 0) {
0704: endRev = fsfs.getYoungestRevision();
0705: }
0706: try {
0707: dump(fsfs, SVNFileUtil.DUMMY_OUT, startRev, endRev, false,
0708: false);
0709: } catch (IOException ioe) {
0710: SVNErrorMessage err = SVNErrorMessage.create(
0711: SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
0712: SVNErrorManager.error(err, ioe);
0713: }
0714: }
0715:
0716: /**
0717: * Dumps contents of the repository to the provided output stream in a
0718: * 'dumpfile' portable format.
0719: *
0720: * <p>
0721: * On each revision dumped this method fires an {@link SVNAdminEvent}
0722: * with action set to {@link SVNAdminEventAction#REVISION_DUMPED} to the registered
0723: * {@link ISVNAdminEventHandler} (if any). To register your <b>ISVNAdminEventHandler</b>
0724: * pass it to {@link #setEventHandler(ISVNEventHandler)}. For this operation the following
0725: * information can be retrieved out of {@link SVNAdminEvent}:
0726: * <ul>
0727: * <li>dumped revision - use {@link SVNAdminEvent#getRevision() SVNAdminEvent.getRevision()} to get it</li>
0728: * </ul>
0729: *
0730: * @param repositoryRoot a repository root directory path
0731: * @param dumpStream an output stream to write dumped contents to
0732: * @param startRevision the first revision to start dumping from
0733: * @param endRevision the last revision to end dumping at
0734: * @param isIncremental if <span class="javakeyword">true</span>
0735: * then the first revision dumped will be a
0736: * diff against the previous revision; otherwise
0737: * the first revision is a fulltext.
0738: * @param useDeltas if <span class="javakeyword">true</span>
0739: * deltas will be written instead of fulltexts
0740: * @throws SVNException
0741: * @since 1.1.1
0742: */
0743: public void doDump(File repositoryRoot, OutputStream dumpStream,
0744: SVNRevision startRevision, SVNRevision endRevision,
0745: boolean isIncremental, boolean useDeltas)
0746: throws SVNException {
0747: FSFS fsfs = SVNAdminHelper.openRepository(repositoryRoot);
0748: long youngestRevision = fsfs.getYoungestRevision();
0749:
0750: long lowerR = SVNAdminHelper.getRevisionNumber(startRevision,
0751: youngestRevision, fsfs);
0752: long upperR = SVNAdminHelper.getRevisionNumber(endRevision,
0753: youngestRevision, fsfs);
0754:
0755: if (!SVNRevision.isValidRevisionNumber(lowerR)) {
0756: lowerR = 0;
0757: upperR = youngestRevision;
0758: } else if (!SVNRevision.isValidRevisionNumber(upperR)) {
0759: upperR = lowerR;
0760: }
0761:
0762: if (lowerR > upperR) {
0763: SVNErrorMessage err = SVNErrorMessage.create(
0764: SVNErrorCode.CL_ARG_PARSING_ERROR,
0765: "First revision cannot be higher than second");
0766: SVNErrorManager.error(err);
0767: }
0768:
0769: try {
0770: dump(fsfs, dumpStream, lowerR, upperR, isIncremental,
0771: useDeltas);
0772: } catch (IOException ioe) {
0773: SVNErrorMessage err = SVNErrorMessage.create(
0774: SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
0775: SVNErrorManager.error(err, ioe);
0776: }
0777: }
0778:
0779: /**
0780: * Reads the provided dump stream committing new revisions to a repository.
0781: *
0782: * <p>
0783: * On each revision loaded this method fires an {@link SVNAdminEvent}
0784: * with action set to {@link SVNAdminEventAction#REVISION_LOADED} to the registered
0785: * {@link ISVNAdminEventHandler} (if any). To register your <b>ISVNAdminEventHandler</b>
0786: * pass it to {@link #setEventHandler(ISVNEventHandler)}. For this operation the following
0787: * information can be retrieved out of {@link SVNAdminEvent}:
0788: * <ul>
0789: * <li>original revision - use {@link SVNAdminEvent#getOriginalRevision() SVNAdminEvent.getOriginalRevision()} to get it</li>
0790: * <li>new committed revision - use {@link SVNAdminEvent#getRevision() SVNAdminEvent.getRevision()} to get it</li>
0791: * </ul>
0792: *
0793: * <p>
0794: * A call to this method is equivalent to
0795: * <code>doLoad(repositoryRoot, dumpStream, false, false, SVNUUIDAction.DEFAULT, null)</code>.
0796: *
0797: * @param repositoryRoot the root directory path of the repository where
0798: * new revisions will be committed
0799: * @param dumpStream stream with dumped contents of a repository
0800: * @throws SVNException
0801: * @see #doLoad(File, InputStream, boolean, boolean, SVNUUIDAction, String)
0802: * @since 1.1.1
0803: */
0804: public void doLoad(File repositoryRoot, InputStream dumpStream)
0805: throws SVNException {
0806: doLoad(repositoryRoot, dumpStream, false, false,
0807: SVNUUIDAction.DEFAULT, null);
0808: }
0809:
0810: /**
0811: * Reads the provided dump stream committing new revisions to a repository.
0812: *
0813: * <p>
0814: * On each revision loaded this method fires an {@link SVNAdminEvent}
0815: * with action set to {@link SVNAdminEventAction#REVISION_LOADED} to the registered
0816: * {@link ISVNAdminEventHandler} (if any). To register your <b>ISVNAdminEventHandler</b>
0817: * pass it to {@link #setEventHandler(ISVNEventHandler)}. For this operation the following
0818: * information can be retrieved out of {@link SVNAdminEvent}:
0819: * <ul>
0820: * <li>original revision - use {@link SVNAdminEvent#getOriginalRevision() SVNAdminEvent.getOriginalRevision()} to get it</li>
0821: * <li>new committed revision - use {@link SVNAdminEvent#getRevision() SVNAdminEvent.getRevision()} to get it</li>
0822: * </ul>
0823: *
0824: * @param repositoryRoot the root directory path of the repository where
0825: * new revisions will be committed
0826: * @param dumpStream stream with dumped contents of a repository
0827: * @param usePreCommitHook if <span class="javakeyword">true</span>
0828: * then calls a pre-commit hook before committing
0829: * @param usePostCommitHook if <span class="javakeyword">true</span>
0830: * then calls a post-commit hook after committing
0831: * @param uuidAction one of the three possible ways to treat uuids
0832: * @param parentDir if not <span class="javakeyword">null</span>
0833: * then loads at this directory in the repository
0834: * @throws SVNException
0835: * @since 1.1.1
0836: */
0837: public void doLoad(File repositoryRoot, InputStream dumpStream,
0838: boolean usePreCommitHook, boolean usePostCommitHook,
0839: SVNUUIDAction uuidAction, String parentDir)
0840: throws SVNException {
0841: CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
0842: ISVNLoadHandler handler = getLoadHandler(repositoryRoot,
0843: usePreCommitHook, usePostCommitHook, uuidAction,
0844: parentDir, decoder);
0845:
0846: String line = null;
0847: int version = -1;
0848: StringBuffer buffer = new StringBuffer();
0849: try {
0850: line = SVNFileUtil.readLineFromStream(dumpStream, buffer,
0851: decoder);
0852: if (line == null) {
0853: SVNAdminHelper.generateIncompleteDataError();
0854: }
0855:
0856: //parse format
0857: if (!line.startsWith(SVNAdminHelper.DUMPFILE_MAGIC_HEADER
0858: + ":")) {
0859: SVNErrorMessage err = SVNErrorMessage.create(
0860: SVNErrorCode.STREAM_MALFORMED_DATA,
0861: "Malformed dumpfile header");
0862: SVNErrorManager.error(err);
0863: }
0864:
0865: try {
0866: line = line
0867: .substring(SVNAdminHelper.DUMPFILE_MAGIC_HEADER
0868: .length() + 1);
0869: line = line.trim();
0870: version = Integer.parseInt(line);
0871: if (version > SVNAdminHelper.DUMPFILE_FORMAT_VERSION) {
0872: SVNErrorMessage err = SVNErrorMessage
0873: .create(
0874: SVNErrorCode.STREAM_MALFORMED_DATA,
0875: "Unsupported dumpfile version: {0,number,integer}",
0876: new Integer(version));
0877: SVNErrorManager.error(err);
0878: }
0879: } catch (NumberFormatException nfe) {
0880: SVNErrorMessage err = SVNErrorMessage.create(
0881: SVNErrorCode.STREAM_MALFORMED_DATA,
0882: "Malformed dumpfile header");
0883: SVNErrorManager.error(err, nfe);
0884: }
0885:
0886: while (true) {
0887: checkCancelled();
0888: boolean foundNode = false;
0889:
0890: //skip empty lines
0891: buffer.setLength(0);
0892: line = SVNFileUtil.readLineFromStream(dumpStream,
0893: buffer, decoder);
0894: if (line == null) {
0895: if (buffer.length() > 0) {
0896: SVNAdminHelper.generateIncompleteDataError();
0897: } else {
0898: break;
0899: }
0900: }
0901:
0902: if (line.length() == 0
0903: || Character.isWhitespace(line.charAt(0))) {
0904: continue;
0905: }
0906:
0907: Map headers = readHeaderBlock(dumpStream, line, decoder);
0908: if (headers
0909: .containsKey(SVNAdminHelper.DUMPFILE_REVISION_NUMBER)) {
0910: handler.closeRevision();
0911: handler.openRevision(headers);
0912: } else if (headers
0913: .containsKey(SVNAdminHelper.DUMPFILE_NODE_PATH)) {
0914: handler.openNode(headers);
0915: foundNode = true;
0916: } else if (headers
0917: .containsKey(SVNAdminHelper.DUMPFILE_UUID)) {
0918: String uuid = (String) headers
0919: .get(SVNAdminHelper.DUMPFILE_UUID);
0920: handler.parseUUID(uuid);
0921: } else if (headers
0922: .containsKey(SVNAdminHelper.DUMPFILE_MAGIC_HEADER)) {
0923: try {
0924: version = Integer
0925: .parseInt((String) headers
0926: .get(SVNAdminHelper.DUMPFILE_MAGIC_HEADER));
0927: } catch (NumberFormatException nfe) {
0928: SVNErrorMessage err = SVNErrorMessage.create(
0929: SVNErrorCode.STREAM_MALFORMED_DATA,
0930: "Malformed dumpfile header");
0931: SVNErrorManager.error(err, nfe);
0932: }
0933: } else {
0934: SVNErrorMessage err = SVNErrorMessage.create(
0935: SVNErrorCode.STREAM_MALFORMED_DATA,
0936: "Unrecognized record type in stream");
0937: SVNErrorManager.error(err);
0938: }
0939:
0940: String contentLength = (String) headers
0941: .get(SVNAdminHelper.DUMPFILE_CONTENT_LENGTH);
0942: String propContentLength = (String) headers
0943: .get(SVNAdminHelper.DUMPFILE_PROP_CONTENT_LENGTH);
0944: String textContentLength = (String) headers
0945: .get(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_LENGTH);
0946:
0947: boolean isOldVersion = version == 1
0948: && contentLength != null
0949: && propContentLength == null
0950: && textContentLength == null;
0951: int actualPropLength = 0;
0952: if (propContentLength != null || isOldVersion) {
0953: String delta = (String) headers
0954: .get(SVNAdminHelper.DUMPFILE_PROP_DELTA);
0955: boolean isDelta = delta != null
0956: && "true".equals(delta);
0957:
0958: if (foundNode && !isDelta) {
0959: handler.removeNodeProperties();
0960: }
0961:
0962: int length = 0;
0963: try {
0964: length = Integer
0965: .parseInt(propContentLength != null ? propContentLength
0966: : contentLength);
0967: } catch (NumberFormatException nfe) {
0968: SVNErrorMessage err = SVNErrorMessage
0969: .create(
0970: SVNErrorCode.STREAM_MALFORMED_DATA,
0971: "Malformed dumpfile header: can't parse property block length header");
0972: SVNErrorManager.error(err, nfe);
0973: }
0974: actualPropLength += handler.parsePropertyBlock(
0975: dumpStream, length, foundNode);
0976: }
0977:
0978: if (textContentLength != null) {
0979: String delta = (String) headers
0980: .get(SVNAdminHelper.DUMPFILE_TEXT_DELTA);
0981: boolean isDelta = delta != null
0982: && "true".equals(delta);
0983: int length = 0;
0984: try {
0985: length = Integer.parseInt(textContentLength);
0986: } catch (NumberFormatException nfe) {
0987: SVNErrorMessage err = SVNErrorMessage
0988: .create(
0989: SVNErrorCode.STREAM_MALFORMED_DATA,
0990: "Malformed dumpfile header: can't parse text block length header");
0991: SVNErrorManager.error(err, nfe);
0992: }
0993: handler.parseTextBlock(dumpStream, length, isDelta);
0994: } else if (isOldVersion) {
0995: int length = 0;
0996: try {
0997: length = Integer.parseInt(contentLength);
0998: } catch (NumberFormatException nfe) {
0999: SVNErrorMessage err = SVNErrorMessage
1000: .create(
1001: SVNErrorCode.STREAM_MALFORMED_DATA,
1002: "Malformed dumpfile header: can't parse content length header");
1003: SVNErrorManager.error(err, nfe);
1004: }
1005:
1006: length -= actualPropLength;
1007:
1008: if (length > 0
1009: || SVNNodeKind
1010: .parseKind((String) headers
1011: .get(SVNAdminHelper.DUMPFILE_NODE_KIND)) == SVNNodeKind.FILE) {
1012: handler.parseTextBlock(dumpStream, length,
1013: false);
1014: }
1015: }
1016:
1017: if (contentLength != null && !isOldVersion) {
1018: int remaining = 0;
1019: try {
1020: remaining = Integer.parseInt(contentLength);
1021: } catch (NumberFormatException nfe) {
1022: SVNErrorMessage err = SVNErrorMessage
1023: .create(
1024: SVNErrorCode.STREAM_MALFORMED_DATA,
1025: "Malformed dumpfile header: can't parse content length header");
1026: SVNErrorManager.error(err, nfe);
1027: }
1028:
1029: int propertyContentLength = 0;
1030: if (propContentLength != null) {
1031: try {
1032: propertyContentLength = Integer
1033: .parseInt(propContentLength);
1034: } catch (NumberFormatException nfe) {
1035: SVNErrorMessage err = SVNErrorMessage
1036: .create(
1037: SVNErrorCode.STREAM_MALFORMED_DATA,
1038: "Malformed dumpfile header: can't parse property block length header");
1039: SVNErrorManager.error(err, nfe);
1040: }
1041: }
1042: remaining -= propertyContentLength;
1043:
1044: int txtContentLength = 0;
1045: if (textContentLength != null) {
1046: try {
1047: txtContentLength = Integer
1048: .parseInt(textContentLength);
1049: } catch (NumberFormatException nfe) {
1050: SVNErrorMessage err = SVNErrorMessage
1051: .create(
1052: SVNErrorCode.STREAM_MALFORMED_DATA,
1053: "Malformed dumpfile header: can't parse text block length header");
1054: SVNErrorManager.error(err, nfe);
1055: }
1056: }
1057: remaining -= txtContentLength;
1058:
1059: if (remaining < 0) {
1060: SVNErrorMessage err = SVNErrorMessage
1061: .create(
1062: SVNErrorCode.STREAM_MALFORMED_DATA,
1063: "Sum of subblock sizes larger than total block content length");
1064: SVNErrorManager.error(err);
1065: }
1066:
1067: byte buf[] = new byte[SVNAdminHelper.STREAM_CHUNK_SIZE];
1068: while (remaining > 0) {
1069: int numToRead = remaining >= SVNAdminHelper.STREAM_CHUNK_SIZE ? SVNAdminHelper.STREAM_CHUNK_SIZE
1070: : remaining;
1071: int numRead = dumpStream
1072: .read(buf, 0, numToRead);
1073:
1074: remaining -= numRead;
1075: if (numRead != numToRead) {
1076: SVNAdminHelper
1077: .generateIncompleteDataError();
1078: }
1079: }
1080: }
1081:
1082: if (foundNode) {
1083: handler.closeNode();
1084: foundNode = false;
1085: }
1086: }
1087:
1088: handler.closeRevision();
1089:
1090: } catch (IOException ioe) {
1091: SVNErrorMessage err = SVNErrorMessage.create(
1092: SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
1093: SVNErrorManager.error(err, ioe);
1094: }
1095: }
1096:
1097: private void dump(FSFS fsfs, OutputStream dumpStream, long start,
1098: long end, boolean isIncremental, boolean useDeltas)
1099: throws SVNException, IOException {
1100: boolean isDumping = dumpStream != null;
1101: long youngestRevision = fsfs.getYoungestRevision();
1102:
1103: if (!SVNRevision.isValidRevisionNumber(start)) {
1104: start = 0;
1105: }
1106:
1107: if (!SVNRevision.isValidRevisionNumber(end)) {
1108: end = youngestRevision;
1109: }
1110:
1111: if (dumpStream == null) {
1112: dumpStream = SVNFileUtil.DUMMY_OUT;
1113: }
1114:
1115: if (start > end) {
1116: SVNErrorMessage err = SVNErrorMessage
1117: .create(
1118: SVNErrorCode.REPOS_BAD_ARGS,
1119: "Start revision {0,number,integer} is greater than end revision {1,number,integer}",
1120: new Object[] { new Long(start),
1121: new Long(end) });
1122: SVNErrorManager.error(err);
1123: }
1124:
1125: if (end > youngestRevision) {
1126: SVNErrorMessage err = SVNErrorMessage
1127: .create(
1128: SVNErrorCode.REPOS_BAD_ARGS,
1129: "End revision {0,number,integer} is invalid (youngest revision is {1,number,integer})",
1130: new Object[] { new Long(end),
1131: new Long(youngestRevision) });
1132: SVNErrorManager.error(err);
1133: }
1134:
1135: if (start == 0 && isIncremental) {
1136: isIncremental = false;
1137: }
1138:
1139: String uuid = fsfs.getUUID();
1140: int version = SVNAdminHelper.DUMPFILE_FORMAT_VERSION;
1141:
1142: if (!useDeltas) {
1143: //for compatibility with SVN 1.0.x
1144: version--;
1145: }
1146:
1147: writeDumpData(dumpStream, SVNAdminHelper.DUMPFILE_MAGIC_HEADER
1148: + ": " + version + "\n\n");
1149: writeDumpData(dumpStream, SVNAdminHelper.DUMPFILE_UUID + ": "
1150: + uuid + "\n\n");
1151:
1152: for (long i = start; i <= end; i++) {
1153: long fromRev, toRev;
1154:
1155: checkCancelled();
1156:
1157: if (i == start && !isIncremental) {
1158: if (i == 0) {
1159: writeRevisionRecord(dumpStream, fsfs, 0);
1160: toRev = 0;
1161: String message = (isDumping ? "* Dumped"
1162: : "* Verified")
1163: + " revision " + toRev + ".";
1164: if (myEventHandler != null) {
1165: SVNAdminEvent event = new SVNAdminEvent(toRev,
1166: SVNAdminEventAction.REVISION_DUMPED,
1167: message);
1168: myEventHandler.handleAdminEvent(event,
1169: ISVNEventHandler.UNKNOWN);
1170: }
1171: continue;
1172: }
1173:
1174: fromRev = 0;
1175: toRev = i;
1176: } else {
1177: fromRev = i - 1;
1178: toRev = i;
1179: }
1180:
1181: writeRevisionRecord(dumpStream, fsfs, toRev);
1182: boolean useDeltasForRevision = useDeltas
1183: && (isIncremental || i != start);
1184: FSRevisionRoot toRoot = fsfs.createRevisionRoot(toRev);
1185: ISVNEditor dumpEditor = new SVNDumpEditor(fsfs, toRoot,
1186: toRev, start, "/", dumpStream, useDeltasForRevision);
1187:
1188: if (i == start && !isIncremental) {
1189: FSRevisionRoot fromRoot = fsfs
1190: .createRevisionRoot(fromRev);
1191: SVNAdminHelper.deltifyDir(fsfs, fromRoot, "/", "",
1192: toRoot, "/", dumpEditor);
1193: } else {
1194: FSRepositoryUtil.replay(fsfs, toRoot, "", -1, false,
1195: dumpEditor);
1196: }
1197: String message = (isDumping ? "* Dumped" : "* Verified")
1198: + " revision " + toRev + ".";
1199: if (myEventHandler != null) {
1200: SVNAdminEvent event = new SVNAdminEvent(toRev,
1201: SVNAdminEventAction.REVISION_DUMPED, message);
1202: myEventHandler.handleAdminEvent(event,
1203: ISVNEventHandler.UNKNOWN);
1204: }
1205: }
1206: }
1207:
1208: private void writeRevisionRecord(OutputStream dumpStream,
1209: FSFS fsfs, long revision) throws SVNException, IOException {
1210: Map revProps = fsfs.getRevisionProperties(revision);
1211:
1212: String revisionDate = (String) revProps
1213: .get(SVNRevisionProperty.DATE);
1214: if (revisionDate != null) {
1215: SVNDate date = SVNDate.parseDatestamp(revisionDate);
1216: revProps.put(SVNRevisionProperty.DATE, date.format());
1217: }
1218:
1219: ByteArrayOutputStream encodedProps = new ByteArrayOutputStream();
1220: SVNAdminHelper.writeProperties(revProps, null, encodedProps);
1221:
1222: writeDumpData(dumpStream,
1223: SVNAdminHelper.DUMPFILE_REVISION_NUMBER + ": "
1224: + revision + "\n");
1225: String propContents = new String(encodedProps.toByteArray(),
1226: "UTF-8");
1227: writeDumpData(dumpStream,
1228: SVNAdminHelper.DUMPFILE_PROP_CONTENT_LENGTH + ": "
1229: + propContents.length() + "\n");
1230: writeDumpData(dumpStream,
1231: SVNAdminHelper.DUMPFILE_CONTENT_LENGTH + ": "
1232: + propContents.length() + "\n\n");
1233: writeDumpData(dumpStream, propContents);
1234: dumpStream.write('\n');
1235: }
1236:
1237: private void writeDumpData(OutputStream out, String data)
1238: throws IOException {
1239: out.write(data.getBytes("UTF-8"));
1240: }
1241:
1242: private ISVNLoadHandler getLoadHandler(File repositoryRoot,
1243: boolean usePreCommitHook, boolean usePostCommitHook,
1244: SVNUUIDAction uuidAction, String parentDir,
1245: CharsetDecoder decoder) throws SVNException {
1246: if (myLoadHandler == null) {
1247: FSFS fsfs = SVNAdminHelper.openRepository(repositoryRoot);
1248: DefaultLoadHandler handler = new DefaultLoadHandler(
1249: usePreCommitHook, usePostCommitHook, uuidAction,
1250: parentDir, myEventHandler, decoder);
1251: handler.setFSFS(fsfs);
1252: myLoadHandler = handler;
1253: } else {
1254: myLoadHandler.setUsePreCommitHook(usePreCommitHook);
1255: myLoadHandler.setUsePostCommitHook(usePostCommitHook);
1256: myLoadHandler.setUUIDAction(uuidAction);
1257: myLoadHandler.setParentDir(parentDir);
1258: }
1259:
1260: return myLoadHandler;
1261: }
1262:
1263: private Map readHeaderBlock(InputStream dumpStream,
1264: String firstHeader, CharsetDecoder decoder)
1265: throws SVNException, IOException {
1266: Map headers = new HashMap();
1267: StringBuffer buffer = new StringBuffer();
1268:
1269: while (true) {
1270: String header = null;
1271: buffer.setLength(0);
1272: if (firstHeader != null) {
1273: header = firstHeader;
1274: firstHeader = null;
1275: } else {
1276: header = SVNFileUtil.readLineFromStream(dumpStream,
1277: buffer, decoder);
1278: if (header == null && buffer.length() > 0) {
1279: SVNAdminHelper.generateIncompleteDataError();
1280: } else if (buffer.length() == 0) {
1281: break;
1282: }
1283: }
1284:
1285: int colonInd = header.indexOf(':');
1286: if (colonInd == -1) {
1287: SVNErrorMessage err = SVNErrorMessage
1288: .create(
1289: SVNErrorCode.STREAM_MALFORMED_DATA,
1290: "Dump stream contains a malformed header (with no '':'') at ''{0}''",
1291: header.length() > 20 ? header
1292: .substring(0, 19) : header);
1293: SVNErrorManager.error(err);
1294: }
1295:
1296: String name = header.substring(0, colonInd);
1297: if (colonInd + 2 > header.length()) {
1298: SVNErrorMessage err = SVNErrorMessage
1299: .create(
1300: SVNErrorCode.STREAM_MALFORMED_DATA,
1301: "Dump stream contains a malformed header (with no value) at ''{0}''",
1302: header.length() > 20 ? header
1303: .substring(0, 19) : header);
1304: SVNErrorManager.error(err);
1305: }
1306: String value = header.substring(colonInd + 2);
1307: headers.put(name, value);
1308: }
1309:
1310: return headers;
1311: }
1312:
1313: private void copyRevisionProperties(SVNRepository fromRepository,
1314: SVNRepository toRepository, long revision, boolean sync)
1315: throws SVNException {
1316: Map existingRevProps = null;
1317: if (sync) {
1318: existingRevProps = toRepository.getRevisionProperties(
1319: revision, null);
1320: }
1321:
1322: boolean sawSyncProperties = false;
1323: Map revProps = fromRepository.getRevisionProperties(revision,
1324: null);
1325: for (Iterator propNames = revProps.keySet().iterator(); propNames
1326: .hasNext();) {
1327: String propName = (String) propNames.next();
1328: String propValue = (String) revProps.get(propName);
1329: if (propName.startsWith("sync-")) {
1330: sawSyncProperties = true;
1331: } else {
1332: toRepository.setRevisionPropertyValue(revision,
1333: propName, propValue);
1334: }
1335:
1336: if (sync) {
1337: existingRevProps.remove(propName);
1338: }
1339: }
1340:
1341: if (sync) {
1342: for (Iterator propNames = existingRevProps.keySet()
1343: .iterator(); propNames.hasNext();) {
1344: String propName = (String) propNames.next();
1345: toRepository.setRevisionPropertyValue(revision,
1346: propName, null);
1347: }
1348: }
1349:
1350: if (sawSyncProperties) {
1351: SVNDebugLog.getDefaultLog().info(
1352: "Copied properties for revision " + revision
1353: + " (sync-* properties skipped).\n");
1354: } else {
1355: SVNDebugLog.getDefaultLog().info(
1356: "Copied properties for revision " + revision
1357: + ".\n");
1358: }
1359: }
1360:
1361: private SessionInfo openSourceRepository(SVNRepository targetRepos)
1362: throws SVNException {
1363: String fromURL = targetRepos.getRevisionPropertyValue(0,
1364: SVNRevisionProperty.FROM_URL);
1365: String fromUUID = targetRepos.getRevisionPropertyValue(0,
1366: SVNRevisionProperty.FROM_UUID);
1367: String lastMergedRev = targetRepos.getRevisionPropertyValue(0,
1368: SVNRevisionProperty.LAST_MERGED_REVISION);
1369:
1370: if (fromURL == null || fromUUID == null
1371: || lastMergedRev == null) {
1372: SVNErrorMessage err = SVNErrorMessage.create(
1373: SVNErrorCode.IO_ERROR,
1374: "Destination repository has not been initialized");
1375: SVNErrorManager.error(err);
1376: }
1377:
1378: SVNURL srcURL = SVNURL.parseURIDecoded(fromURL);
1379: // TOOD close session.
1380: SVNRepository srcRepos = createRepository(srcURL, false);
1381:
1382: checkIfRepositoryIsAtRoot(srcRepos, srcURL);
1383:
1384: String reposUUID = srcRepos.getRepositoryUUID(true);
1385: if (!fromUUID.equals(reposUUID)) {
1386: SVNErrorMessage err = SVNErrorMessage
1387: .create(
1388: SVNErrorCode.IO_ERROR,
1389: "UUID of destination repository ({0}) does not match expected UUID ({1})",
1390: new String[] { reposUUID, fromUUID });
1391: SVNErrorManager.error(err);
1392: }
1393:
1394: return new SessionInfo(srcRepos, Long.parseLong(lastMergedRev));
1395: }
1396:
1397: private void checkIfRepositoryIsAtRoot(SVNRepository repos,
1398: SVNURL url) throws SVNException {
1399: SVNURL reposRoot = repos.getRepositoryRoot(true);
1400: if (!reposRoot.equals(url)) {
1401: SVNErrorMessage err = SVNErrorMessage
1402: .create(
1403: SVNErrorCode.IO_ERROR,
1404: "Session is rooted at ''{0}'' but the repos root is ''{1}''",
1405: new SVNURL[] { url, reposRoot });
1406: SVNErrorManager.error(err);
1407: }
1408: }
1409:
1410: private void lock(SVNRepository repos) throws SVNException {
1411: String hostName = null;
1412: try {
1413: hostName = InetAddress.getLocalHost().getHostName();
1414: } catch (UnknownHostException e) {
1415: SVNErrorMessage err = SVNErrorMessage.create(
1416: SVNErrorCode.IO_ERROR, "Can't get local hostname");
1417: SVNErrorManager.error(err, e);
1418: }
1419:
1420: if (hostName.length() > 256) {
1421: hostName = hostName.substring(0, 256);
1422: }
1423:
1424: String lockToken = hostName
1425: + ":"
1426: + SVNUUIDGenerator.formatUUID(SVNUUIDGenerator
1427: .generateUUID());
1428: int i = 0;
1429: for (i = 0; i < 10; i++) {
1430: String reposLockToken = repos.getRevisionPropertyValue(0,
1431: SVNRevisionProperty.LOCK);
1432: if (reposLockToken != null) {
1433: if (reposLockToken.equals(lockToken)) {
1434: return;
1435: }
1436: try {
1437: Thread.sleep(1000);
1438: } catch (InterruptedException e) {
1439: //
1440: }
1441: } else {
1442: repos.setRevisionPropertyValue(0,
1443: SVNRevisionProperty.LOCK, lockToken);
1444: }
1445: }
1446:
1447: SVNErrorMessage err = SVNErrorMessage
1448: .create(
1449: SVNErrorCode.IO_ERROR,
1450: "Couldn''t get lock on destination repos after {0,number,integer} attempts\n",
1451: new Integer(i));
1452: SVNErrorManager.error(err);
1453: }
1454:
1455: private void unlock(SVNRepository repos) throws SVNException {
1456: repos.setRevisionPropertyValue(0, SVNRevisionProperty.LOCK,
1457: null);
1458: }
1459:
1460: private class SessionInfo {
1461:
1462: SVNRepository myRepository;
1463: long myLastMergedRevision;
1464:
1465: public SessionInfo(SVNRepository repos, long lastMergedRev) {
1466: myRepository = repos;
1467: myLastMergedRevision = lastMergedRev;
1468: }
1469: }
1470: }
|