/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.wc; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Iterator; import java.util.Map; import org.tmatesoft.svn.core.SVNCommitInfo; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.internal.delta.SVNDeltaCombiner; import org.tmatesoft.svn.core.internal.io.fs.CountingOutputStream; import org.tmatesoft.svn.core.internal.io.fs.FSFS; import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryUtil; import org.tmatesoft.svn.core.internal.io.fs.FSRevisionNode; import org.tmatesoft.svn.core.internal.io.fs.FSRevisionRoot; import org.tmatesoft.svn.core.internal.io.fs.FSRoot; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.admin.SVNChecksumInputStream; import org.tmatesoft.svn.core.io.ISVNDeltaConsumer; import org.tmatesoft.svn.core.io.ISVNEditor; import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator; import org.tmatesoft.svn.core.io.diff.SVNDiffWindow; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class SVNDumpEditor implements ISVNEditor { private FSRoot myRoot; private FSFS myFSFS; private long myTargetRevision; private long myOldestDumpedRevision; private String myRootPath; private OutputStream myDumpStream; private boolean myUseDeltas; private boolean myIsVerify; private DirectoryInfo myCurrentDirInfo; private SVNDeltaCombiner myDeltaCombiner; private SVNDeltaGenerator myDeltaGenerator; public SVNDumpEditor(FSFS fsfs, FSRoot root, long toRevision, long oldestDumpedRevision, String rootPath, OutputStream dumpStream, boolean useDeltas, boolean isVerify) { myRoot = root; myFSFS = fsfs; myTargetRevision = toRevision; myOldestDumpedRevision = oldestDumpedRevision; myRootPath = rootPath; myDumpStream = dumpStream; myUseDeltas = useDeltas; myIsVerify = isVerify; } public void reset(FSFS fsfs, FSRoot root, long toRevision, long oldestDumpedRevision, String rootPath, OutputStream dumpStream, boolean useDeltas, boolean isVerify) { myRoot = root; myFSFS = fsfs; myTargetRevision = toRevision; myOldestDumpedRevision = oldestDumpedRevision; myRootPath = rootPath; myDumpStream = dumpStream; myUseDeltas = useDeltas; myIsVerify = isVerify; myCurrentDirInfo = null; } public void abortEdit() throws SVNException { } public void absentDir(String path) throws SVNException { } public void absentFile(String path) throws SVNException { } public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException { DirectoryInfo parent = myCurrentDirInfo; myCurrentDirInfo = createDirectoryInfo(path, copyFromPath, copyFromRevision, parent); boolean isDeleted = parent.myDeletedEntries.containsKey(path); boolean isCopy = copyFromPath != null && SVNRevision.isValidRevisionNumber(copyFromRevision); dumpNode(path, SVNNodeKind.DIR, isDeleted ? SVNAdminHelper.NODE_ACTION_REPLACE : SVNAdminHelper.NODE_ACTION_ADD, isCopy, isCopy ? copyFromPath : null, isCopy ? copyFromRevision : -1); if (isDeleted) { parent.myDeletedEntries.remove(path); } myCurrentDirInfo.myIsWrittenOut = true; } public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { boolean isCopy = copyFromPath != null && SVNRevision.isValidRevisionNumber(copyFromRevision); boolean isDeleted = myCurrentDirInfo.myDeletedEntries.containsKey(path); dumpNode(path, SVNNodeKind.FILE, isDeleted ? SVNAdminHelper.NODE_ACTION_REPLACE : SVNAdminHelper.NODE_ACTION_ADD, isCopy, isCopy ? copyFromPath : null, isCopy ? copyFromRevision : -1); if (isDeleted) { myCurrentDirInfo.myDeletedEntries.remove(path); } } public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException { if (!myCurrentDirInfo.myIsWrittenOut) { dumpNode(myCurrentDirInfo.myFullPath, SVNNodeKind.DIR, SVNAdminHelper.NODE_ACTION_CHANGE, false, myCurrentDirInfo.myComparePath, myCurrentDirInfo.myCompareRevision); myCurrentDirInfo.myIsWrittenOut = true; } } public void changeFileProperty(String path, String name, SVNPropertyValue value) throws SVNException { } public void closeDir() throws SVNException { if (myIsVerify) { FSRevisionNode node = myRoot.getRevisionNode(myCurrentDirInfo.myFullPath); Map entries = node.getDirEntries(myFSFS); for (Iterator entriesIter = entries.keySet().iterator(); entriesIter.hasNext();) { String entryName = (String) entriesIter.next(); String entryPath = SVNPathUtil.append(myCurrentDirInfo.myFullPath, entryName); SVNNodeKind kind = myRoot.checkNodeKind(entryPath); FSRevisionNode entryNode = myRoot.getRevisionNode(entryPath); if (kind == SVNNodeKind.DIR) { entryNode.getDirEntries(myFSFS); } else if (kind == SVNNodeKind.FILE) { entryNode.getFileLength(); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND, "Unexpected node kind {0} for ''{1}''", new Object[] { kind, entryPath }); SVNErrorManager.error(err, SVNLogType.FSFS); } } } for (Iterator entries = myCurrentDirInfo.myDeletedEntries.keySet().iterator(); entries.hasNext();) { String path = (String) entries.next(); dumpNode(path, SVNNodeKind.UNKNOWN, SVNAdminHelper.NODE_ACTION_DELETE, false, null, -1); } myCurrentDirInfo = myCurrentDirInfo.myParentInfo; } public SVNCommitInfo closeEdit() throws SVNException { return null; } public void closeFile(String path, String textChecksum) throws SVNException { } public void deleteEntry(String path, long revision) throws SVNException { myCurrentDirInfo.myDeletedEntries.put(path, path); } public void openDir(String path, long revision) throws SVNException { DirectoryInfo parent = myCurrentDirInfo; String cmpPath = null; long cmpRev = -1; if (parent != null && parent.myComparePath != null && SVNRevision.isValidRevisionNumber(parent.myCompareRevision)) { cmpPath = SVNPathUtil.append(parent.myComparePath, SVNPathUtil.tail(path)); cmpRev = parent.myCompareRevision; } myCurrentDirInfo = createDirectoryInfo(path, cmpPath, cmpRev, parent); } public void openFile(String path, long revision) throws SVNException { String cmpPath = null; long cmpRev = -1; if (myCurrentDirInfo != null && myCurrentDirInfo.myComparePath != null && SVNRevision.isValidRevisionNumber(myCurrentDirInfo.myCompareRevision)) { cmpPath = SVNPathUtil.append(myCurrentDirInfo.myComparePath, SVNPathUtil.tail(path)); cmpRev = myCurrentDirInfo.myCompareRevision; } dumpNode(path, SVNNodeKind.FILE, SVNAdminHelper.NODE_ACTION_CHANGE, false, cmpPath, cmpRev); } public void openRoot(long revision) throws SVNException { myCurrentDirInfo = createDirectoryInfo(null, null, -1, null); } public void targetRevision(long revision) throws SVNException { } public void applyTextDelta(String path, String baseChecksum) throws SVNException { } public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { return null; } public void textDeltaEnd(String path) throws SVNException { } private void dumpNode(String path, SVNNodeKind kind, int nodeAction, boolean isCopy, String cmpPath, long cmpRev) throws SVNException { File tmpFile = null; try { writeDumpData(SVNAdminHelper.DUMPFILE_NODE_PATH + ": " + (path.startsWith("/") ? path.substring(1) : path) + "\n"); if (kind == SVNNodeKind.FILE) { writeDumpData(SVNAdminHelper.DUMPFILE_NODE_KIND + ": file\n"); } else if (kind == SVNNodeKind.DIR) { writeDumpData(SVNAdminHelper.DUMPFILE_NODE_KIND + ": dir\n"); } if (cmpPath != null) { cmpPath = cmpPath.startsWith("/") ? cmpPath.substring(1) : cmpPath; } String comparePath = path; long compareRevision = myTargetRevision - 1; if (cmpPath != null && SVNRevision.isValidRevisionNumber(cmpRev)) { comparePath = cmpPath; compareRevision = cmpRev; } comparePath = SVNPathUtil.canonicalizePath(comparePath); comparePath = SVNPathUtil.getAbsolutePath(comparePath); FSRevisionRoot compareRoot = null; boolean mustDumpProps = false; boolean mustDumpText = false; String canonicalPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.canonicalizePath(path)); switch(nodeAction) { case SVNAdminHelper.NODE_ACTION_CHANGE: writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": change\n"); compareRoot = myFSFS.createRevisionRoot(compareRevision); mustDumpProps = FSRepositoryUtil.arePropertiesChanged(compareRoot, comparePath, myRoot, canonicalPath); if (kind == SVNNodeKind.FILE) { mustDumpText = FSRepositoryUtil.areFileContentsChanged(compareRoot, comparePath, myRoot, canonicalPath); } break; case SVNAdminHelper.NODE_ACTION_REPLACE: if (!isCopy) { writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": replace\n"); if (kind == SVNNodeKind.FILE) { mustDumpText = true; } mustDumpProps = true; } else { writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": delete\n\n"); dumpNode(path, kind, SVNAdminHelper.NODE_ACTION_ADD, isCopy, comparePath, compareRevision); mustDumpText = false; mustDumpProps = false; } break; case SVNAdminHelper.NODE_ACTION_DELETE: writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": delete\n"); mustDumpText = false; mustDumpProps = false; break; case SVNAdminHelper.NODE_ACTION_ADD: writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": add\n"); if (!isCopy) { if (kind == SVNNodeKind.FILE) { mustDumpText = true; } mustDumpProps = true; } else { if (!myIsVerify && cmpRev < myOldestDumpedRevision) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.FSFS, "WARNING: Referencing data in revision " + cmpRev + ", which is older than the oldest\nWARNING: dumped revision (" + myOldestDumpedRevision + "). Loading this dump into an empty repository\nWARNING: will fail.\n"); } writeDumpData(SVNAdminHelper.DUMPFILE_NODE_COPYFROM_REVISION + ": " + cmpRev + "\n"); writeDumpData(SVNAdminHelper.DUMPFILE_NODE_COPYFROM_PATH + ": " + cmpPath + "\n"); compareRoot = myFSFS.createRevisionRoot(compareRevision); mustDumpProps = FSRepositoryUtil.arePropertiesChanged(compareRoot, comparePath, myRoot, canonicalPath); if (kind == SVNNodeKind.FILE) { mustDumpText = FSRepositoryUtil.areFileContentsChanged(compareRoot, comparePath, myRoot, canonicalPath); FSRevisionNode revNode = compareRoot.getRevisionNode(comparePath); String checkSum = revNode.getFileMD5Checksum(); if (checkSum != null && checkSum.length() > 0) { writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_COPY_SOURCE_MD5 + ": " + checkSum + "\n"); } checkSum = revNode.getFileSHA1Checksum(); if (checkSum != null && checkSum.length() > 0) { writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_COPY_SOURCE_SHA1 + ": " + checkSum + "\n"); } } } break; } if (!mustDumpProps && !mustDumpText) { writeDumpData("\n\n"); return; } long contentLength = 0; String propContents = null; if (mustDumpProps) { FSRevisionNode node = myRoot.getRevisionNode(canonicalPath); SVNProperties props = node.getProperties(myFSFS); SVNProperties oldProps = null; if (myUseDeltas && compareRoot != null) { FSRevisionNode cmpNode = compareRoot.getRevisionNode(comparePath); oldProps = cmpNode.getProperties(myFSFS); writeDumpData(SVNAdminHelper.DUMPFILE_PROP_DELTA + ": true\n"); } ByteArrayOutputStream encodedProps = new ByteArrayOutputStream(); SVNAdminHelper.writeProperties(props, oldProps, encodedProps); propContents = new String(encodedProps.toByteArray(), "UTF-8"); contentLength += propContents.length(); writeDumpData(SVNAdminHelper.DUMPFILE_PROP_CONTENT_LENGTH + ": " + propContents.length() + "\n"); } if (mustDumpText && kind == SVNNodeKind.FILE) { long txtLength = 0; FSRevisionNode node = myRoot.getRevisionNode(canonicalPath); if (myUseDeltas) { tmpFile = SVNFileUtil.createTempFile("dump", ".tmp"); InputStream sourceStream = null; InputStream targetStream = null; OutputStream tmpStream = null; SVNDeltaCombiner deltaCombiner = getDeltaCombiner(); SVNDeltaGenerator deltaGenerator = getDeltaGenerator(); try { if (compareRoot != null && comparePath != null) { sourceStream = compareRoot.getFileStreamForPath(deltaCombiner, comparePath); } else { sourceStream = SVNFileUtil.DUMMY_IN; } targetStream = myRoot.getFileStreamForPath(deltaCombiner, canonicalPath); tmpStream = SVNFileUtil.openFileForWriting(tmpFile); final CountingOutputStream countingStream = new CountingOutputStream(tmpStream, 0); ISVNDeltaConsumer consumer = new ISVNDeltaConsumer() { private boolean isHeaderWritten = false; public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { try { if (diffWindow != null) { diffWindow.writeTo(countingStream, !isHeaderWritten, false); } else { SVNDiffWindow.EMPTY.writeTo(countingStream, !isHeaderWritten, false); } } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } isHeaderWritten = true; return SVNFileUtil.DUMMY_OUT; } public void applyTextDelta(String path, String baseChecksum) throws SVNException { } public void textDeltaEnd(String path) throws SVNException { } }; deltaGenerator.sendDelta(null, sourceStream, 0, targetStream, consumer, false); txtLength = countingStream.getPosition(); if (compareRoot != null) { FSRevisionNode revNode = compareRoot.getRevisionNode(comparePath); String hexDigest = revNode.getFileMD5Checksum(); if (hexDigest != null && hexDigest.length() > 0) { writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_DELTA_BASE_MD5 + ": " + hexDigest + "\n"); } hexDigest = revNode.getFileSHA1Checksum(); if (hexDigest == null) { hexDigest = computeSHA1Checksum(compareRoot, comparePath); } if (hexDigest != null && hexDigest.length() > 0) { writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_DELTA_BASE_SHA1 + ": " + hexDigest + "\n"); } } } finally { SVNFileUtil.closeFile(sourceStream); SVNFileUtil.closeFile(targetStream); SVNFileUtil.closeFile(tmpStream); } writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_DELTA + ": true\n"); } else { txtLength = node.getFileLength(); } contentLength += txtLength; writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_LENGTH + ": " + txtLength + "\n"); String checksum = node.getFileMD5Checksum(); if (checksum != null && checksum.length() > 0) { writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_MD5 + ": " + checksum + "\n"); } checksum = node.getFileSHA1Checksum(); if (checksum == null) { checksum = computeSHA1Checksum(myRoot, canonicalPath); } if (checksum != null && checksum.length() > 0) { writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_SHA1 + ": " + checksum + "\n"); } } writeDumpData(SVNAdminHelper.DUMPFILE_CONTENT_LENGTH + ": " + contentLength + "\n\n"); if (mustDumpProps) { writeDumpData(propContents); } if (mustDumpText && kind == SVNNodeKind.FILE) { InputStream source = null; try { if (tmpFile != null) { source = SVNFileUtil.openFileForReading(tmpFile, SVNLogType.WC); } else { source = myRoot.getFileStreamForPath(getDeltaCombiner(), canonicalPath); } //TODO: provide canceller? FSRepositoryUtil.copy(source, myDumpStream, null); } finally { SVNFileUtil.closeFile(source); } } writeDumpData("\n\n"); } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } finally { SVNFileUtil.deleteFile(tmpFile); } } private String computeSHA1Checksum(FSRoot revision, String filePath) throws SVNException { InputStream is = revision.getFileStreamForPath(getDeltaCombiner(), filePath); SVNChecksumInputStream checksum = null; try { checksum = new SVNChecksumInputStream(is, "SHA1"); } finally { SVNFileUtil.closeFile(checksum); } return checksum != null ? checksum.getDigest() : null; } private SVNDeltaGenerator getDeltaGenerator() { if (myDeltaGenerator == null) { myDeltaGenerator = new SVNDeltaGenerator(); } return myDeltaGenerator; } private SVNDeltaCombiner getDeltaCombiner() { if (myDeltaCombiner == null) { myDeltaCombiner = new SVNDeltaCombiner(); } else { myDeltaCombiner.reset(); } return myDeltaCombiner; } private DirectoryInfo createDirectoryInfo(String path, String copyFromPath, long copyFromRev, DirectoryInfo parent) { String fullPath = null; if (parent != null) { fullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(myRootPath, path)); } else { fullPath = myRootPath; } String cmpPath = null; if (copyFromPath != null) { cmpPath = copyFromPath.startsWith("/") ? copyFromPath.substring(1) : copyFromPath; } return new DirectoryInfo(fullPath, cmpPath, copyFromRev, parent); } private void writeDumpData(String data) throws IOException { myDumpStream.write(data.getBytes("UTF-8")); } private class DirectoryInfo { String myFullPath; String myComparePath; long myCompareRevision; boolean myIsWrittenOut; Map myDeletedEntries; DirectoryInfo myParentInfo; public DirectoryInfo(String path, String cmpPath, long cmpRev, DirectoryInfo parent) { myFullPath = path; myParentInfo = parent; myComparePath = cmpPath; myCompareRevision = cmpRev; myDeletedEntries = new SVNHashMap(); myIsWrittenOut = false; } } }