/* * ==================================================================== * 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.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.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.internal.delta.SVNDeltaCombiner; 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.io.fs.FSTransactionRoot; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc2.ng.ISvnPropertiesDiffHandler; import org.tmatesoft.svn.core.io.ISVNEditor; import org.tmatesoft.svn.core.io.SVNLocationEntry; import org.tmatesoft.svn.core.io.diff.SVNDiffWindow; import org.tmatesoft.svn.core.wc.ISVNDiffGenerator; import org.tmatesoft.svn.core.wc.ISVNEventHandler; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.admin.ISVNChangeEntryHandler; import org.tmatesoft.svn.core.wc.admin.ISVNChangedDirectoriesHandler; import org.tmatesoft.svn.core.wc.admin.ISVNGNUDiffGenerator; import org.tmatesoft.svn.core.wc.admin.SVNChangeEntry; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class SVNNodeEditor implements ISVNEditor { private static final char TYPE_REPLACED = 'R'; private Node myCurrentNode; private Node myRootNode; private FSRoot myBaseRoot; private FSFS myFSFS; private Map myFiles; private ISVNEventHandler myCancelHandler; private File myTempDirectory; public SVNNodeEditor(FSFS fsfs, FSRoot baseRoot, ISVNEventHandler handler) { myBaseRoot = baseRoot; myFSFS = fsfs; myCancelHandler = handler; myFiles = new SVNHashMap(); } 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 { myCurrentNode = addOrOpen(path, SVNChangeEntry.TYPE_ADDED, SVNNodeKind.DIR, myCurrentNode, copyFromPath, copyFromRevision); } public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { Node node = addOrOpen(path, SVNChangeEntry.TYPE_ADDED, SVNNodeKind.FILE, myCurrentNode, copyFromPath, copyFromRevision); myFiles.put(path, node); } public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException { myCurrentNode.myHasPropModifications = true; } public void changeFileProperty(String path, String name, SVNPropertyValue value) throws SVNException { Node fileNode = (Node) myFiles.get(path); fileNode.myHasPropModifications = true; } public void closeDir() throws SVNException { myCurrentNode = myCurrentNode.myParent; } public SVNCommitInfo closeEdit() throws SVNException { return null; } public void closeFile(String path, String textChecksum) throws SVNException { myFiles.remove(path); } public void deleteEntry(String path, long revision) throws SVNException { String name = SVNPathUtil.tail(path); Node node = null; if (myCurrentNode != null && myCurrentNode.myChildren != null) { for (Iterator children = myCurrentNode.myChildren.iterator(); children.hasNext();) { Node child = (Node) children.next(); if (child.myName.equals(name)) { node = child; break; } } } if (node == null) { if (myCurrentNode != null) { node = new Node(); node.myName = name; node.myParent = myCurrentNode; if (myCurrentNode.myChildren == null) { myCurrentNode.myChildren = new LinkedList(); } myCurrentNode.myChildren.add(node); } } node.myAction = SVNChangeEntry.TYPE_DELETED; SVNLocationEntry baseLocation = findRealBaseLocation(node); FSRoot baseRoot = null; if (!SVNRevision.isValidRevisionNumber(baseLocation.getRevision())) { baseRoot = myBaseRoot; } else { baseRoot = myFSFS.createRevisionRoot(baseLocation.getRevision()); } SVNNodeKind kind = baseRoot.checkNodeKind(baseLocation.getPath()); if (kind == SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "''{0}'' not found in filesystem", path); SVNErrorManager.error(err, SVNLogType.FSFS); } node.myKind = kind; } public void openDir(String path, long revision) throws SVNException { myCurrentNode = addOrOpen(path, TYPE_REPLACED, SVNNodeKind.DIR, myCurrentNode, null, -1); } public void openFile(String path, long revision) throws SVNException { Node node = addOrOpen(path, TYPE_REPLACED, SVNNodeKind.FILE, myCurrentNode, null, -1); myFiles.put(path, node); } public void openRoot(long revision) throws SVNException { myRootNode = myCurrentNode = new Node(); myCurrentNode.myName = ""; myCurrentNode.myParent = null; myCurrentNode.myKind = SVNNodeKind.DIR; myCurrentNode.myAction = TYPE_REPLACED; } public void targetRevision(long revision) throws SVNException { } public void applyTextDelta(String path, String baseChecksum) throws SVNException { Node fileNode = (Node) myFiles.get(path); fileNode.myHasTextModifications = true; } public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { return null; } public void textDeltaEnd(String path) throws SVNException { } public void diff(FSRoot root, long baseRevision, ISVNGNUDiffGenerator generator, OutputStream os) throws SVNException { if (myRootNode != null) { FSRevisionRoot baseRoot = root.getOwner().createRevisionRoot(baseRevision); try { diffImpl(root, baseRoot, "/", "/", myRootNode, generator, os); } finally { cleanup(); } } } public void traverseTree(boolean includeCopyInfo, ISVNChangeEntryHandler handler) throws SVNException { if (myRootNode != null) { traverseChangedTreeImpl(myRootNode, "/", includeCopyInfo, handler); } } public void traverseChangedDirs(ISVNChangedDirectoriesHandler handler) throws SVNException { if (myRootNode != null) { traverseChangedDirsImpl(myRootNode, "/", handler); } } private void diffImpl(FSRoot root, FSRevisionRoot baseRoot, String path, String basePath, Node node, ISVNGNUDiffGenerator generator, OutputStream os) throws SVNException { if (myCancelHandler != null) { myCancelHandler.checkCancelled(); } DefaultSVNGNUDiffGenerator defaultGenerator = null; if (generator instanceof DefaultSVNGNUDiffGenerator) { defaultGenerator = (DefaultSVNGNUDiffGenerator) generator; defaultGenerator.setHeaderWritten(false); defaultGenerator.setDiffWritten(false); } boolean isCopy = false; boolean printedHeader = false; if (SVNRevision.isValidRevisionNumber(node.myCopyFromRevision) && node.myCopyFromPath != null) { basePath = node.myCopyFromPath; generator.displayHeader(ISVNGNUDiffGenerator.COPIED, path, basePath, node.myCopyFromRevision, os); baseRoot = myFSFS.createRevisionRoot(node.myCopyFromRevision); isCopy = true; printedHeader = true; } boolean doDiff = false; boolean isOriginalEmpty = false; DiffItem originalFile = null; DiffItem newFile = null; if (node.myKind == SVNNodeKind.FILE) { if (node.myAction == TYPE_REPLACED && node.myHasTextModifications) { doDiff = true; originalFile = prepareTmpFile(baseRoot, basePath, generator); newFile = prepareTmpFile(root, path, generator); } else if (generator.isDiffCopied() && node.myAction == SVNChangeEntry.TYPE_ADDED && isCopy) { if (node.myHasTextModifications) { doDiff = true; originalFile = prepareTmpFile(baseRoot, basePath, generator); newFile = prepareTmpFile(root, path, generator); } } else if (generator.isDiffAdded() && node.myAction == SVNChangeEntry.TYPE_ADDED) { doDiff = true; isOriginalEmpty = true; originalFile = prepareTmpFile(null, basePath, generator); newFile = prepareTmpFile(root, path, generator); } else if (generator.isDiffDeleted() && node.myAction == SVNChangeEntry.TYPE_DELETED) { doDiff = true; originalFile = prepareTmpFile(null, basePath, generator); newFile = prepareTmpFile(null, path, generator); } if (!printedHeader && (node.myAction != TYPE_REPLACED || node.myHasTextModifications)) { if (node.myAction == SVNChangeEntry.TYPE_ADDED) { generator.displayHeader(ISVNGNUDiffGenerator.ADDED, path, null, -1, os); } else if (node.myAction == SVNChangeEntry.TYPE_DELETED) { generator.displayHeader(ISVNGNUDiffGenerator.DELETED, path, null, -1, os); } else if (node.myAction == TYPE_REPLACED) { generator.displayHeader(ISVNGNUDiffGenerator.MODIFIED, path, null, -1, os); } printedHeader = true; } } SVNProperties baseProps = null; SVNProperties propsDiff = null; String displayPath = null; if (node.myHasPropModifications && node.myAction != SVNChangeEntry.TYPE_DELETED) { FSRevisionNode localNode = root.getRevisionNode(path); SVNProperties props = localNode.getProperties(root.getOwner()); if (node.myAction != SVNChangeEntry.TYPE_ADDED) { FSRevisionNode baseNode = baseRoot.getRevisionNode(basePath); baseProps = baseNode.getProperties(baseRoot.getOwner()); } propsDiff = FSRepositoryUtil.getPropsDiffs(baseProps, props); if (propsDiff.size() > 0) { displayPath = path.startsWith("/") ? path.substring(1) : path; } } if (doDiff) { if (defaultGenerator != null) { if (isOriginalEmpty) { defaultGenerator.setOriginalFile(null, path); } else { defaultGenerator.setOriginalFile(baseRoot, basePath); } defaultGenerator.setNewFile(root, path); } String rev1 = isOriginalEmpty ? "(rev 0)" : "(rev " + baseRoot.getRevision() + ")"; String rev2 = null; if (root instanceof FSRevisionRoot) { FSRevisionRoot revRoot = (FSRevisionRoot) root; rev2 = "(rev " + revRoot.getRevision() + ")"; } else { FSTransactionRoot txnRoot = (FSTransactionRoot) root; rev2 = "(txn " + txnRoot.getTxnID() + ")"; } if (generator instanceof ISvnPropertiesDiffHandler) { ((ISvnPropertiesDiffHandler)generator).handlePropertiesDiff(baseProps, propsDiff); } generator.displayFileDiff(path, originalFile.myTmpFile, newFile.myTmpFile, rev1, rev2, originalFile.myMimeType, newFile.myMimeType, os); boolean hasDiff = defaultGenerator != null ? defaultGenerator.isDiffWritten() : true; if (!hasDiff && !node.myHasPropModifications && (node.myAction == SVNChangeEntry.TYPE_ADDED && generator.isDiffAdded() || node.myAction == SVNChangeEntry.TYPE_DELETED && generator.isDiffDeleted())) { int kind = node.myAction == SVNChangeEntry.TYPE_ADDED ? ISVNGNUDiffGenerator.ADDED : ISVNGNUDiffGenerator.DELETED; defaultGenerator.setHeaderWritten(false); defaultGenerator.displayHeader(kind, path, null, -1, os); defaultGenerator.printHeader(os); } } else if (printedHeader) { generator.displayHeader(ISVNGNUDiffGenerator.NO_DIFF, path, null, -1, os); } if (propsDiff != null && displayPath != null) { generator.displayPropDiff(displayPath, baseProps, propsDiff, os); } if (node.myChildren == null || node.myChildren.size() == 0) { return; } for (Iterator children = node.myChildren.iterator(); children.hasNext();) { Node childNode = (Node) children.next(); String childPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(path, childNode.myName)); String childBasePath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(basePath, childNode.myName)); diffImpl(root, baseRoot, childPath, childBasePath, childNode, generator, os); } } private DiffItem prepareTmpFile(FSRoot root, String path, ISVNDiffGenerator generator) throws SVNException { String mimeType = null; if (root != null) { FSRevisionNode node = root.getRevisionNode(path); SVNProperties nodeProps = node.getProperties(root.getOwner()); mimeType = nodeProps.getStringValue(SVNProperty.MIME_TYPE); if (SVNProperty.isBinaryMimeType(mimeType) && !generator.isForcedBinaryDiff()) { return new DiffItem(mimeType, null); } } File tmpFile = createTempFile(generator); if (root != null) { InputStream contents = null; OutputStream tmpOS = null; try { contents = root.getFileStreamForPath(new SVNDeltaCombiner(), path); tmpOS = SVNFileUtil.openFileForWriting(tmpFile); FSRepositoryUtil.copy(contents, tmpOS, myCancelHandler); } finally { SVNFileUtil.closeFile(contents); SVNFileUtil.closeFile(tmpOS); } } return new DiffItem(mimeType, tmpFile); } private File createTempFile(ISVNDiffGenerator generator) throws SVNException { File tmpFile = null; try { return File.createTempFile("diff.", ".tmp", getTempDirectory(generator)); } catch (IOException e) { SVNFileUtil.deleteFile(tmpFile); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage()); SVNErrorManager.error(err, SVNLogType.DEFAULT); } return null; } private File getTempDirectory(ISVNDiffGenerator generator) throws SVNException { if (myTempDirectory == null || !myTempDirectory.exists()) { myTempDirectory = generator.createTempDirectory(); } return myTempDirectory; } private void cleanup() { if (myTempDirectory != null) { SVNFileUtil.deleteAll(myTempDirectory, true); } } private void traverseChangedDirsImpl(Node node, String path, ISVNChangedDirectoriesHandler handler) throws SVNException { if (myCancelHandler != null) { myCancelHandler.checkCancelled(); } if (node == null || node.myKind != SVNNodeKind.DIR) { return; } boolean proceed = node.myHasPropModifications; if (!proceed && node.myChildren != null) { for (Iterator children = node.myChildren.iterator(); children.hasNext() && !proceed;) { Node child = (Node) children.next(); if (child.myKind == SVNNodeKind.FILE || child.myHasTextModifications || child.myAction == SVNChangeEntry.TYPE_ADDED || child.myAction == SVNChangeEntry.TYPE_DELETED) { proceed = true; } } } if (proceed && handler != null) { handler.handleDir(path); } if (node.myChildren == null || node.myChildren.size() == 0) { return; } for (Iterator children = node.myChildren.iterator(); children.hasNext();) { Node childNode = (Node) children.next(); String fullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(path, childNode.myName)); traverseChangedDirsImpl(childNode, fullPath, handler); } } private void traverseChangedTreeImpl(Node node, String path, boolean includeCopyInfo, ISVNChangeEntryHandler handler) throws SVNException { if (myCancelHandler != null) { myCancelHandler.checkCancelled(); } if (node == null) { return; } SVNChangeEntry changeEntry = null; if (node.myAction == SVNChangeEntry.TYPE_ADDED) { String copyFromPath = includeCopyInfo ? node.myCopyFromPath : null; long copyFromRevision = includeCopyInfo ? node.myCopyFromRevision : -1; changeEntry = new SVNChangeEntry(path, node.myKind, node.myAction, copyFromPath, copyFromRevision, false, false); } else if (node.myAction == SVNChangeEntry.TYPE_DELETED) { changeEntry = new SVNChangeEntry(path, node.myKind, node.myAction, null, -1, false, false); } else if (node.myAction == TYPE_REPLACED) { if (node.myHasPropModifications || node.myHasTextModifications) { changeEntry = new SVNChangeEntry(path, node.myKind, SVNChangeEntry.TYPE_UPDATED, null, -1, node.myHasTextModifications, node.myHasPropModifications); } } if (changeEntry != null && handler != null) { handler.handleEntry(changeEntry); } if (node.myChildren == null || node.myChildren.size() == 0) { return; } for (Iterator children = node.myChildren.iterator(); children.hasNext();) { Node childNode = (Node) children.next(); String fullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(path, childNode.myName)); traverseChangedTreeImpl(childNode, fullPath, includeCopyInfo, handler); } } private SVNLocationEntry findRealBaseLocation(Node node) throws SVNException { if (node.myAction == SVNChangeEntry.TYPE_ADDED && node.myCopyFromPath != null && SVNRevision.isValidRevisionNumber(node.myCopyFromRevision)) { return new SVNLocationEntry(node.myCopyFromRevision, node.myCopyFromPath); } if (node.myParent != null) { SVNLocationEntry location = findRealBaseLocation(node.myParent); return new SVNLocationEntry(location.getRevision(), SVNPathUtil.getAbsolutePath(SVNPathUtil.append(location.getPath(), node.myName))); } return new SVNLocationEntry(-1, "/"); } private Node addOrOpen(String path, char action, SVNNodeKind kind, Node parent, String copyFromPath, long copyFromRevision) { if (parent.myChildren == null) { parent.myChildren = new LinkedList(); } Node node = new Node(); node.myName = SVNPathUtil.tail(path); node.myAction = action; node.myKind = kind; node.myCopyFromPath = copyFromPath; node.myCopyFromRevision = copyFromRevision; node.myParent = parent; parent.myChildren.add(node); return node; } private class Node { SVNNodeKind myKind; char myAction; boolean myHasTextModifications; boolean myHasPropModifications; String myName; long myCopyFromRevision; String myCopyFromPath; Node myParent; LinkedList myChildren; } private class DiffItem { String myMimeType; File myTmpFile; public DiffItem(String mimeType, File tmpFile) { myMimeType = mimeType; myTmpFile = tmpFile; } } }