/* * ==================================================================== * 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.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; 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.SVNProperty; import org.tmatesoft.svn.core.SVNRevisionProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.internal.io.fs.FSEntry; 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.util.SVNPathUtil; import org.tmatesoft.svn.core.io.ISVNEditor; import org.tmatesoft.svn.core.io.SVNRepository; 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 SVNAdminHelper { public static int writeRevisionProperties(SVNRepository toRepository, long revision, SVNProperties revProps) throws SVNException { int filteredCount = 0; for (Iterator propNamesIter = revProps.nameSet().iterator(); propNamesIter.hasNext();) { String propName = (String) propNamesIter.next(); SVNPropertyValue propValue = revProps.getSVNPropertyValue(propName); if (propName.startsWith(SVNProperty.SVN_SYNC_PREFIX)) { filteredCount++; } else { toRepository.setRevisionPropertyValue(revision, propName, propValue); } } return filteredCount; } public static void removePropertiesNotInSource(SVNRepository repository, long revision, SVNProperties sourceProps, SVNProperties targetProps) throws SVNException { for (Iterator propNamesIter = targetProps.nameSet().iterator(); propNamesIter.hasNext();) { String propName = (String) propNamesIter.next(); if (sourceProps.getSVNPropertyValue(propName) == null) { repository.setRevisionPropertyValue(revision, propName, null); } } } public static FSFS openRepository(File reposRootPath, boolean openFS) throws SVNException { FSFS fsfs = new FSFS(reposRootPath); if (openFS) { fsfs.open(); } else { fsfs.openRoot(); fsfs.getFSType(); } return fsfs; } public static void closeRepository(FSFS fsfs) { if (fsfs != null) { try { fsfs.close(); } catch (SVNException e) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.FSFS, e); SVNDebugLog.getDefaultLog().logFine(SVNLogType.FSFS, e.getMessage()); } } } public static FSFS openRepositoryForRecovery(File reposRootPath) throws SVNException { FSFS fsfs = new FSFS(reposRootPath); fsfs.openForRecovery(); return fsfs; } public static long getRevisionNumber(SVNRevision revision, long youngestRevision, FSFS fsfs) throws SVNException { long revNumber = -1; if (revision.getNumber() >= 0) { revNumber = revision.getNumber(); } else if (revision == SVNRevision.HEAD) { revNumber = youngestRevision; } else if (revision.getDate() != null) { revNumber = fsfs.getDatedRevision(revision.getDate()); } else if (revision != SVNRevision.UNDEFINED) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_ARG_PARSING_ERROR, "Invalid revision specifier"); SVNErrorManager.error(err, SVNLogType.FSFS); } if (revNumber > youngestRevision) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_ARG_PARSING_ERROR, "Revisions must not be greater than the youngest revision ({0})", new Long(youngestRevision)); SVNErrorManager.error(err, SVNLogType.FSFS); } return revNumber; } public static void writeProperties(SVNProperties props, SVNProperties oldProps, OutputStream dumpStream) throws SVNException { LinkedList propNames = new LinkedList(); for(Iterator names = props.nameSet().iterator(); names.hasNext();) { String propName = (String) names.next(); if (SVNRevisionProperty.LOG.equals(propName)) { propNames.addFirst(propName); } else if (SVNRevisionProperty.AUTHOR.equals(propName)) { if (propNames.contains(SVNRevisionProperty.LOG)) { int ind = propNames.indexOf(SVNRevisionProperty.LOG); propNames.add(ind + 1, propName); } else { propNames.addFirst(propName); } } else { propNames.addLast(propName); } } for(Iterator names = propNames.iterator(); names.hasNext();) { String propName = (String) names.next(); SVNPropertyValue propValue = props.getSVNPropertyValue(propName); if (oldProps != null) { SVNPropertyValue oldValue = oldProps.getSVNPropertyValue(propName); if (oldValue != null && oldValue.equals(propValue)) { continue; } } SVNWCProperties.appendProperty(propName, propValue, dumpStream); } if (oldProps != null) { for(Iterator names = oldProps.nameSet().iterator(); names.hasNext();) { String propName = (String) names.next(); if (props.containsName(propName)) { continue; } SVNWCProperties.appendPropertyDeleted(propName, dumpStream); } } try { byte[] terminator = "PROPS-END\n".getBytes("UTF-8"); dumpStream.write(terminator); } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } } public static void deltifyDir(FSFS fsfs, FSRevisionRoot srcRoot, String srcParentDir, String srcEntry, FSRevisionRoot tgtRoot, String tgtFullPath, ISVNEditor editor) throws SVNException { if (srcParentDir == null) { generateNotADirError("source parent", srcParentDir); } if (tgtFullPath == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_PATH_SYNTAX, "Invalid target path"); SVNErrorManager.error(err, SVNLogType.FSFS); } String srcFullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(srcParentDir, srcEntry)); SVNNodeKind tgtKind = tgtRoot.checkNodeKind(tgtFullPath); SVNNodeKind srcKind = srcRoot.checkNodeKind(srcFullPath); if (tgtKind == SVNNodeKind.NONE && srcKind == SVNNodeKind.NONE) { editor.closeEdit(); return; } if (srcEntry == null && (srcKind != SVNNodeKind.DIR || tgtKind != SVNNodeKind.DIR)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_PATH_SYNTAX, "Invalid editor anchoring; at least one of the input paths is not a directory and there was no source entry"); SVNErrorManager.error(err, SVNLogType.FSFS); } editor.targetRevision(tgtRoot.getRevision()); long rootRevision = srcRoot.getRevision(); if (tgtKind == SVNNodeKind.NONE) { editor.openRoot(rootRevision); editor.deleteEntry(srcEntry, -1); editor.closeDir(); editor.closeEdit(); return; } if (srcKind == SVNNodeKind.NONE) { editor.openRoot(rootRevision); addFileOrDir(fsfs, editor, srcRoot, tgtRoot, tgtFullPath, srcEntry, tgtKind); editor.closeDir(); editor.closeEdit(); return; } FSRevisionNode srcNode = srcRoot.getRevisionNode(srcFullPath); FSRevisionNode tgtNode = tgtRoot.getRevisionNode(tgtFullPath); int distance = srcNode.getId().compareTo(tgtNode.getId()); if (distance == 0) { editor.closeEdit(); return; } else if (srcEntry != null) { if (srcKind != tgtKind || distance == -1) { editor.openRoot(rootRevision); editor.deleteEntry(srcEntry, -1); addFileOrDir(fsfs, editor, srcRoot, tgtRoot, tgtFullPath, srcEntry, tgtKind); } else { editor.openRoot(rootRevision); replaceFileOrDir(fsfs, editor, srcRoot, tgtRoot, srcFullPath, tgtFullPath, srcEntry, tgtKind); } editor.closeDir(); editor.closeEdit(); } else { editor.openRoot(rootRevision); deltifyDirs(fsfs, editor, srcRoot, tgtRoot, srcFullPath, tgtFullPath, ""); editor.closeDir(); editor.closeEdit(); } } public static void generateIncompleteDataError() throws SVNException { SVNDebugLog.getDefaultLog().logFine(SVNLogType.FSFS, new Exception()); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.INCOMPLETE_DATA, "Premature end of content data in dumpstream"); SVNErrorManager.error(err, SVNLogType.FSFS); } public static void generateStreamMalformedError() throws SVNException { SVNDebugLog.getDefaultLog().logFine(SVNLogType.FSFS, new Exception()); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Dumpstream data appears to be malformed"); SVNErrorManager.error(err, SVNLogType.FSFS); } public static int readKeyOrValue(InputStream dumpStream, byte[] buffer, int len) throws SVNException, IOException { int read = 0; while(len - read > 0) { int r = dumpStream.read(buffer, read, len - read); if (r < 0) { break; } read += r; } if (read != len) { SVNAdminHelper.generateIncompleteDataError(); } if (buffer[len - 1] != '\n') { SVNAdminHelper.generateStreamMalformedError(); } return read - 1; } private static void addFileOrDir(FSFS fsfs, ISVNEditor editor, FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String tgtPath, String editPath, SVNNodeKind tgtKind) throws SVNException { if (tgtKind == SVNNodeKind.DIR) { editor.addDir(editPath, null, -1); deltifyDirs(fsfs, editor, srcRoot, tgtRoot, null, tgtPath, editPath); editor.closeDir(); } else { editor.addFile(editPath, null, -1); deltifyFiles(fsfs, editor, srcRoot, tgtRoot, null, tgtPath, editPath); FSRevisionNode tgtNode = tgtRoot.getRevisionNode(tgtPath); editor.closeFile(editPath, tgtNode.getFileMD5Checksum()); } } private static void replaceFileOrDir(FSFS fsfs, ISVNEditor editor, FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String srcPath, String tgtPath, String editPath, SVNNodeKind tgtKind) throws SVNException { long baseRevision = srcRoot.getRevision(); if (tgtKind == SVNNodeKind.DIR) { editor.openDir(editPath, baseRevision); deltifyDirs(fsfs, editor, srcRoot, tgtRoot, srcPath, tgtPath, editPath); editor.closeDir(); } else { editor.openFile(editPath, baseRevision); deltifyFiles(fsfs, editor, srcRoot, tgtRoot, srcPath, tgtPath, editPath); FSRevisionNode tgtNode = tgtRoot.getRevisionNode(tgtPath); editor.closeFile(editPath, tgtNode.getFileMD5Checksum()); } } private static void deltifyFiles(FSFS fsfs, ISVNEditor editor, FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String srcPath, String tgtPath, String editPath) throws SVNException { deltifyProperties(fsfs, editor, srcRoot, tgtRoot, srcPath, tgtPath, editPath, false); boolean changed = false; if (srcPath != null) { changed = FSRepositoryUtil.areFileContentsChanged(srcRoot, srcPath, tgtRoot, tgtPath); } if (changed) { String srcHexDigest = null; if (srcPath != null) { FSRevisionNode srcNode = srcRoot.getRevisionNode(srcPath); srcHexDigest = srcNode.getFileMD5Checksum(); } editor.applyTextDelta(editPath, srcHexDigest); editor.textDeltaChunk(editPath, SVNDiffWindow.EMPTY); } } private static void deltifyDirs(FSFS fsfs, ISVNEditor editor, FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String srcPath, String tgtPath, String editPath) throws SVNException { deltifyProperties(fsfs, editor, srcRoot, tgtRoot, srcPath, tgtPath, editPath, true); FSRevisionNode targetNode = tgtRoot.getRevisionNode(tgtPath); Map targetEntries = targetNode.getDirEntries(fsfs); Map sourceEntries = null; if (srcPath != null) { FSRevisionNode sourceNode = srcRoot.getRevisionNode(srcPath); sourceEntries = sourceNode.getDirEntries(fsfs); } for (Iterator tgtEntries = targetEntries.keySet().iterator(); tgtEntries.hasNext();) { String name = (String) tgtEntries.next(); FSEntry tgtEntry = (FSEntry) targetEntries.get(name); SVNNodeKind tgtKind = tgtEntry.getType(); String targetFullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(tgtPath, tgtEntry.getName())); String editFullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(editPath, tgtEntry.getName())); if (sourceEntries != null && sourceEntries.containsKey(name)) { FSEntry srcEntry = (FSEntry) sourceEntries.get(name); String sourceFullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(srcPath, tgtEntry.getName())); SVNNodeKind srcKind = srcEntry.getType(); int distance = srcEntry.getId().compareTo(tgtEntry.getId()); if (srcKind != tgtKind || distance == -1) { editor.deleteEntry(editFullPath, -1); addFileOrDir(fsfs, editor, srcRoot, tgtRoot, targetFullPath, editFullPath, tgtKind); } else if (distance != 0) { replaceFileOrDir(fsfs, editor, srcRoot, tgtRoot, sourceFullPath, targetFullPath, editFullPath, tgtKind); } sourceEntries.remove(name); } else { addFileOrDir(fsfs, editor, srcRoot, tgtRoot, targetFullPath, editFullPath, tgtKind); } } if (sourceEntries != null) { for (Iterator srcEntries = sourceEntries.keySet().iterator(); srcEntries.hasNext();) { String name = (String) srcEntries.next(); FSEntry entry = (FSEntry) sourceEntries.get(name); String editFullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(editPath, entry.getName())); editor.deleteEntry(editFullPath, -1); } } } private static void deltifyProperties(FSFS fsfs, ISVNEditor editor, FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String srcPath, String tgtPath, String editPath, boolean isDir) throws SVNException { FSRevisionNode targetNode = tgtRoot.getRevisionNode(tgtPath); SVNProperties sourceProps = null; if (srcPath != null) { FSRevisionNode sourceNode = srcRoot.getRevisionNode(srcPath); boolean propsChanged = !FSRepositoryUtil.arePropertiesEqual(sourceNode, targetNode); if (!propsChanged) { return; } sourceProps = sourceNode.getProperties(fsfs); } else { sourceProps = new SVNProperties(); } SVNProperties targetProps = targetNode.getProperties(fsfs); SVNProperties propsDiffs = FSRepositoryUtil.getPropsDiffs(sourceProps, targetProps); Object[] names = propsDiffs.nameSet().toArray(); for (int i = 0; i < names.length; i++) { String propName = (String) names[i]; SVNPropertyValue propValue = propsDiffs.getSVNPropertyValue(propName); if (isDir) { editor.changeDirProperty(propName, propValue); } else { editor.changeFileProperty(editPath, propName, propValue); } } } private static void generateNotADirError(String role, String path) throws SVNException { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_DIRECTORY, "Invalid {0} directory ''{1}''", new Object[]{role, path != null ? path : "(null)"}); SVNErrorManager.error(err, SVNLogType.FSFS); } public static final String DUMPFILE_MAGIC_HEADER = "SVN-fs-dump-format-version"; public static final String DUMPFILE_CONTENT_LENGTH = "Content-length"; public static final String DUMPFILE_NODE_ACTION = "Node-action"; public static final String DUMPFILE_NODE_COPYFROM_PATH = "Node-copyfrom-path"; public static final String DUMPFILE_NODE_COPYFROM_REVISION = "Node-copyfrom-rev"; public static final String DUMPFILE_NODE_KIND = "Node-kind"; public static final String DUMPFILE_NODE_PATH = "Node-path"; public static final String DUMPFILE_PROP_CONTENT_LENGTH = "Prop-content-length"; public static final String DUMPFILE_PROP_DELTA = "Prop-delta"; public static final String DUMPFILE_REVISION_NUMBER = "Revision-number"; public static final String DUMPFILE_TEXT_CONTENT_LENGTH = "Text-content-length"; public static final String DUMPFILE_TEXT_DELTA = "Text-delta"; public static final String DUMPFILE_UUID = "UUID"; public static final String DUMPFILE_TEXT_CONTENT_MD5 = "Text-content-md5"; public static final String DUMPFILE_TEXT_CONTENT_SHA1 = "Text-content-sha1"; public static final String DUMPFILE_TEXT_COPY_SOURCE_MD5 = "Text-copy-source-md5"; public static final String DUMPFILE_TEXT_COPY_SOURCE_SHA1 = "Text-copy-source-sha1"; public static final String DUMPFILE_TEXT_DELTA_BASE_MD5 = "Text-delta-base-md5"; public static final String DUMPFILE_TEXT_DELTA_BASE_SHA1 = "Text-delta-base-sha1"; public static final int DUMPFILE_FORMAT_VERSION = 3; public static final int NODE_ACTION_ADD = 1; public static final int NODE_ACTION_CHANGE = 0; public static final int NODE_ACTION_DELETE = 2; public static final int NODE_ACTION_REPLACE = 3; public static final int NODE_ACTION_UNKNOWN = -1; }