/* * ==================================================================== * 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.util.Iterator; import java.util.Map; import org.tmatesoft.svn.core.SVNDepth; 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.SVNPropertyValue; import org.tmatesoft.svn.core.SVNRevisionProperty; import org.tmatesoft.svn.core.internal.delta.SVNDeltaCombiner; 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.diff.SVNDeltaGenerator; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class SVNAdminDeltifier { private FSFS myFSFS; private SVNDepth myDepth; private boolean myIsIncludeEntryProperties; private boolean myIsIgnoreAncestry; private boolean myIsSendTextDeltas; private ISVNEditor myEditor; private SVNDeltaCombiner myDeltaCombiner; private SVNDeltaGenerator myDeltaGenerator; public SVNAdminDeltifier(FSFS fsfs, SVNDepth depth, boolean includeEntryProperties, boolean ignoreAncestry, boolean sendTextDeltas, ISVNEditor editor) { myFSFS = fsfs; myDepth = depth; myIsIncludeEntryProperties = includeEntryProperties; myIsIgnoreAncestry = ignoreAncestry; myIsSendTextDeltas = sendTextDeltas; myEditor = editor; myDeltaCombiner = new SVNDeltaCombiner(); myDeltaGenerator = new SVNDeltaGenerator(); } public void setEditor(ISVNEditor editor) { myEditor = editor; } public void deltifyDir(FSRevisionRoot srcRoot, String srcParentDir, String srcEntry, FSRevisionRoot tgtRoot, String tgtFullPath) 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) { myEditor.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); } myEditor.targetRevision(tgtRoot.getRevision()); long rootRevision = srcRoot.getRevision(); if (tgtKind == SVNNodeKind.NONE) { myEditor.openRoot(rootRevision); myEditor.deleteEntry(srcEntry, -1); myEditor.closeDir(); myEditor.closeEdit(); return; } if (srcKind == SVNNodeKind.NONE) { myEditor.openRoot(rootRevision); addFileOrDir(srcRoot, tgtRoot, tgtFullPath, srcEntry, tgtKind); myEditor.closeDir(); myEditor.closeEdit(); return; } FSRevisionNode srcNode = srcRoot.getRevisionNode(srcFullPath); FSRevisionNode tgtNode = tgtRoot.getRevisionNode(tgtFullPath); int distance = srcNode.getId().compareTo(tgtNode.getId()); if (distance == 0) { myEditor.closeEdit(); } else if (srcEntry != null) { if (srcKind != tgtKind || distance == -1) { myEditor.openRoot(rootRevision); myEditor.deleteEntry(srcEntry, -1); addFileOrDir(srcRoot, tgtRoot, tgtFullPath, srcEntry, tgtKind); } else { myEditor.openRoot(rootRevision); replaceFileOrDir(srcRoot, tgtRoot, srcFullPath, tgtFullPath, srcEntry, tgtKind); } myEditor.closeDir(); myEditor.closeEdit(); } else { myEditor.openRoot(rootRevision); deltifyDirs(srcRoot, tgtRoot, srcFullPath, tgtFullPath, ""); myEditor.closeDir(); myEditor.closeEdit(); } } private void addFileOrDir(FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String tgtPath, String editPath, SVNNodeKind tgtKind) throws SVNException { if (tgtKind == SVNNodeKind.DIR) { myEditor.addDir(editPath, null, -1); deltifyDirs(srcRoot, tgtRoot, null, tgtPath, editPath); myEditor.closeDir(); } else { myEditor.addFile(editPath, null, -1); deltifyFiles(srcRoot, tgtRoot, null, tgtPath, editPath); FSRevisionNode tgtNode = tgtRoot.getRevisionNode(tgtPath); myEditor.closeFile(editPath, tgtNode.getFileMD5Checksum()); } } private void deltifyDirs(FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String srcPath, String tgtPath, String editPath) throws SVNException { deltifyProperties(srcRoot, tgtRoot, srcPath, tgtPath, editPath, true); FSRevisionNode targetNode = tgtRoot.getRevisionNode(tgtPath); Map targetEntries = targetNode.getDirEntries(myFSFS); Map sourceEntries = null; if (srcPath != null) { FSRevisionNode sourceNode = srcRoot.getRevisionNode(srcPath); sourceEntries = sourceNode.getDirEntries(myFSFS); } 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(); if (myDepth == SVNDepth.INFINITY || srcKind != SVNNodeKind.DIR) { int distance = srcEntry.getId().compareTo(tgtEntry.getId()); if (srcKind != tgtKind || (distance == -1 && !myIsIgnoreAncestry)) { myEditor.deleteEntry(editFullPath, -1); addFileOrDir(srcRoot, tgtRoot, targetFullPath, editFullPath, tgtKind); } else if (distance != 0) { replaceFileOrDir(srcRoot, tgtRoot, sourceFullPath, targetFullPath, editFullPath, tgtKind); } } sourceEntries.remove(name); } else { if (myDepth == SVNDepth.INFINITY || tgtKind != SVNNodeKind.DIR) { addFileOrDir(srcRoot, tgtRoot, targetFullPath, editFullPath, tgtKind); } } } if (sourceEntries != null) { for (Iterator srcEntries = sourceEntries.keySet().iterator(); srcEntries.hasNext();) { String name = (String) srcEntries.next(); FSEntry srcEntry = (FSEntry) sourceEntries.get(name); String editFullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(editPath, srcEntry.getName())); if (myDepth == SVNDepth.INFINITY || srcEntry.getType() != SVNNodeKind.DIR) { myEditor.deleteEntry(editFullPath, -1); } } } } private void replaceFileOrDir(FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String srcPath, String tgtPath, String editPath, SVNNodeKind tgtKind) throws SVNException { long baseRevision = srcRoot.getRevision(); if (tgtKind == SVNNodeKind.DIR) { myEditor.openDir(editPath, baseRevision); deltifyDirs(srcRoot, tgtRoot, srcPath, tgtPath, editPath); myEditor.closeDir(); } else { myEditor.openFile(editPath, baseRevision); deltifyFiles(srcRoot, tgtRoot, srcPath, tgtPath, editPath); FSRevisionNode tgtNode = tgtRoot.getRevisionNode(tgtPath); myEditor.closeFile(editPath, tgtNode.getFileMD5Checksum()); } } private void deltifyFiles(FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String srcPath, String tgtPath, String editPath) throws SVNException { deltifyProperties(srcRoot, tgtRoot, srcPath, tgtPath, editPath, false); boolean changed = false; if (srcPath != null) { if (myIsIgnoreAncestry) { changed = FSRepositoryUtil.checkFilesDifferent(srcRoot, srcPath, tgtRoot, tgtPath, myDeltaCombiner); } else { changed = FSRepositoryUtil.areFileContentsChanged(srcRoot, srcPath, tgtRoot, tgtPath); } } if (changed) { String srcHexDigest = null; if (srcPath != null) { FSRevisionNode srcNode = srcRoot.getRevisionNode(srcPath); srcHexDigest = srcNode.getFileMD5Checksum(); } FSRepositoryUtil.sendTextDelta(myEditor, editPath, srcPath, srcHexDigest, srcRoot, tgtPath, tgtRoot, myIsSendTextDeltas, myDeltaCombiner, myDeltaGenerator, myFSFS); } } private void deltifyProperties(FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot, String srcPath, String tgtPath, String editPath, boolean isDir) throws SVNException { if (myIsIncludeEntryProperties) { FSRevisionNode node = tgtRoot.getRevisionNode(tgtPath); long committedRevision = node.getCreatedRevision(); if (SVNRevision.isValidRevisionNumber(committedRevision)) { if (isDir) { myEditor.changeDirProperty(SVNProperty.COMMITTED_REVISION, SVNPropertyValue.create(String.valueOf(committedRevision))); } else { myEditor.changeFileProperty(editPath, SVNProperty.COMMITTED_REVISION, SVNPropertyValue.create(String.valueOf(committedRevision))); } SVNProperties revisionProps = myFSFS.getRevisionProperties(committedRevision); String committedDateStr = revisionProps.getStringValue(SVNRevisionProperty.DATE); if (committedDateStr != null || srcPath != null) { if (isDir) { myEditor.changeDirProperty(SVNProperty.COMMITTED_DATE, SVNPropertyValue.create(committedDateStr)); } else { myEditor.changeFileProperty(editPath,SVNProperty.COMMITTED_DATE, SVNPropertyValue.create(committedDateStr)); } } String lastAuthor = revisionProps.getStringValue(SVNRevisionProperty.AUTHOR); if (lastAuthor != null || srcPath != null) { if (isDir) { myEditor.changeDirProperty(SVNProperty.LAST_AUTHOR, SVNPropertyValue.create(lastAuthor)); } else { myEditor.changeFileProperty(editPath,SVNProperty.LAST_AUTHOR, SVNPropertyValue.create(lastAuthor)); } } String uuid = myFSFS.getUUID(); if (isDir) { myEditor.changeDirProperty(SVNProperty.UUID, SVNPropertyValue.create(uuid)); } else { myEditor.changeFileProperty(editPath, SVNProperty.UUID, SVNPropertyValue.create(uuid)); } } } 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(myFSFS); } else { sourceProps = new SVNProperties(); } SVNProperties targetProps = targetNode.getProperties(myFSFS); 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) { myEditor.changeDirProperty(propName, propValue); } else { myEditor.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); } }