package org.tmatesoft.svn.core.internal.wc2.ng; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc.admin.SVNChecksumOutputStream; import org.tmatesoft.svn.core.io.ISVNEditor; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.diff.SVNDeltaProcessor; import org.tmatesoft.svn.core.io.diff.SVNDiffWindow; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.util.SVNLogType; import java.io.File; import java.io.OutputStream; import java.util.*; public class SvnNgRemoteDiffEditor2 implements ISVNEditor { private long revision; private long targetRevision; private SVNRepository repository; private ISvnDiffCallback2 callback; private SvnDiffCallbackResult result; private SVNDeltaProcessor deltaProcessor; private boolean textDeltas; private DirBaton dirBaton; private FileBaton fileBaton; private Set<File> tempFiles; private File emptyFile; public SvnNgRemoteDiffEditor2(long revision, boolean textDeltas, SVNRepository repository, ISvnDiffCallback2 callback) { this.revision = revision; this.repository = repository; this.callback = callback; this.result = new SvnDiffCallbackResult(); this.textDeltas = textDeltas; this.deltaProcessor = new SVNDeltaProcessor(); this.tempFiles = new HashSet<File>(); } public void targetRevision(long revision) throws SVNException { targetRevision = revision; } public void openRoot(long revision) throws SVNException { dirBaton = new DirBaton("", null, false, revision); dirBaton.leftSource = new SvnDiffSource(this.revision); dirBaton.rightSource = new SvnDiffSource(this.targetRevision); result.reset(); callback.dirOpened(result, SVNFileUtil.createFilePath(""), dirBaton.leftSource, dirBaton.rightSource, null, null); dirBaton.skip = result.skip; dirBaton.skipChildren = result.skipChildren; } public void deleteEntry(String path, long revision) throws SVNException { try { DirBaton pb = dirBaton; if (pb.skipChildren) { return; } SVNNodeKind kind = repository.checkPath(path, this.revision); if (kind == SVNNodeKind.FILE) { diffDeletedFile(path); } else if (kind == SVNNodeKind.DIR) { diffDeletedDirectory(path); } } finally { cleanupTempFiles(); } } public void absentDir(String path) throws SVNException { result.reset(); callback.nodeAbsent(result, SVNFileUtil.createFilePath(path), null); dirBaton.skip = result.skip; dirBaton.skipChildren = result.skipChildren; } public void absentFile(String path) throws SVNException { result.reset(); callback.nodeAbsent(result, SVNFileUtil.createFilePath(path), null); dirBaton.skip = result.skip; dirBaton.skipChildren = result.skipChildren; } public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException { DirBaton pb = dirBaton; dirBaton = new DirBaton(path, pb, true, SVNRepository.INVALID_REVISION); if (pb.skipChildren) { dirBaton.skip = true; dirBaton.skipChildren = true; return; } dirBaton.rightSource = new SvnDiffSource(this.targetRevision); result.reset(); callback.dirOpened(result, SVNFileUtil.createFilePath(dirBaton.path), null, dirBaton.rightSource, null, null); dirBaton.skip = result.skip; dirBaton.skipChildren = result.skipChildren; } public void openDir(String path, long revision) throws SVNException { DirBaton pb = dirBaton; dirBaton = new DirBaton(path, pb, false, revision); if (pb.skipChildren) { dirBaton.skip = true; dirBaton.skipChildren = true; return; } dirBaton.leftSource = new SvnDiffSource(this.revision); dirBaton.rightSource = new SvnDiffSource(this.targetRevision); result.reset(); callback.dirOpened(result, SVNFileUtil.createFilePath(dirBaton.path), dirBaton.leftSource, dirBaton.rightSource, null, null); dirBaton.skip = result.skip; dirBaton.skipChildren = result.skipChildren; } public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException { if (dirBaton.skip) { return; } if (SVNProperty.isWorkingCopyProperty(name)) { return; } else if (SVNProperty.isRegularProperty(name)) { dirBaton.hasPropChange = true; } dirBaton.propChanges.put(name, value); } public void closeDir() throws SVNException { try { boolean sendChanged = false; SVNProperties pristineProps = new SVNProperties(); if ((dirBaton.hasPropChange || dirBaton.added) && !dirBaton.skip) { if (dirBaton.added) { } else { repository.getDir(dirBaton.path, dirBaton.baseRevision, pristineProps, (ISVNDirEntryHandler) null); } if (dirBaton.propChanges.size() > 0) { dirBaton.propChanges = removeNonPropChanges(pristineProps, dirBaton.propChanges); } if (dirBaton.propChanges.size() > 0 || dirBaton.added) { SVNProperties rightProps = new SVNProperties(pristineProps); rightProps.putAll(dirBaton.propChanges); rightProps.removeNullValues(); if (dirBaton.added) { result.reset(); callback.dirAdded(result, SVNFileUtil.createFilePath(dirBaton.path), null, dirBaton.rightSource, null, rightProps, null); dirBaton.skip = result.skip; dirBaton.skipChildren = result.skipChildren; } else { result.reset(); callback.dirChanged(result, SVNFileUtil.createFilePath(dirBaton.path), dirBaton.leftSource, dirBaton.rightSource, pristineProps, rightProps, dirBaton.propChanges, null); dirBaton.skip = result.skip; dirBaton.skipChildren = result.skipChildren; } sendChanged = true; } } if (!dirBaton.skip && !sendChanged) { result.reset(); callback.dirClosed(result, SVNFileUtil.createFilePath(dirBaton.path), dirBaton.leftSource, dirBaton.rightSource, null); dirBaton.skip = result.skip; dirBaton.skipChildren = result.skipChildren; } } finally { dirBaton = dirBaton.parentBaton; } } public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { DirBaton pb = dirBaton; FileBaton fb = fileBaton = new FileBaton(path, pb, true); if (pb.skipChildren) { fb.skip = true; return; } fb.pristineProps = new SVNProperties(); fb.rightSource = new SvnDiffSource(this.targetRevision); result.reset(); callback.fileOpened(result, SVNFileUtil.createFilePath(fb.path), null, fb.rightSource, null, false, null); fb.skip = result.skip; } public void openFile(String path, long revision) throws SVNException { DirBaton pb = dirBaton; FileBaton fb = fileBaton = new FileBaton(path, pb, false); if (pb.skipChildren) { fb.skip = true; return; } fb.baseRevision = revision; fb.leftSource = new SvnDiffSource(this.revision); fb.rightSource = new SvnDiffSource(this.targetRevision); result.reset(); callback.fileOpened(result, SVNFileUtil.createFilePath(fb.path), fb.leftSource, fb.rightSource, null, false, null); fb.skip = result.skip; } public void changeFileProperty(String path, String propertyName, SVNPropertyValue propertyValue) throws SVNException { FileBaton fb = fileBaton; if (fb.skip) { return; } if (SVNProperty.isWorkingCopyProperty(propertyName)) { return; } else if (SVNProperty.isRegularProperty(propertyName)) { fb.hasPropChanges = true; } fb.propChanges.put(propertyName, propertyValue); } public void closeFile(String path, String textChecksum) throws SVNException { try { FileBaton fb = fileBaton; if (fb.skip) { return; } if (textChecksum != null && this.textDeltas) { if (fb.resultMd5Checksum != null && !textChecksum.equals(fb.resultMd5Checksum)) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CHECKSUM_MISMATCH, "Checksum mismatch for ''{0}''", fb.path); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } if (fb.added || (fb.pathEndRevision != null || !this.textDeltas) || fb.hasPropChanges) { SVNProperties rightProps; if (!fb.added && fb.pristineProps == null) { getFileFromRa(fb, true); } String oldChecksum = fb.pristineProps.getStringValue(SVNProperty.CHECKSUM); if (fb.pristineProps != null) { fb.propChanges = removeNonPropChanges(fb.pristineProps, fb.propChanges); } rightProps = new SVNProperties(fb.pristineProps); rightProps.putAll(fb.propChanges); rightProps.removeNullValues(); if (fb.added) { result.reset(); callback.fileAdded(result, SVNFileUtil.createFilePath(fb.path), null, fb.rightSource, null, fb.pathEndRevision, null, rightProps); } else { result.reset(); boolean fileModified = fb.pathEndRevision != null; if (textChecksum != null && oldChecksum != null) { //SVNKit is different from SVN: it always sends applyTextDelta, but Subversion --- only for changed files fileModified = !textChecksum.equals(oldChecksum); } if (fileModified && !textDeltas) { fb.pathStartRevision = getEmptyFile(); fb.pathEndRevision = getEmptyFile(); } callback.fileChanged(result, SVNFileUtil.createFilePath(fb.path), fb.leftSource, fb.rightSource, fb.pathEndRevision != null ? fb.pathStartRevision : null, fb.pathEndRevision, fb.pristineProps, rightProps, fileModified, fb.propChanges); } } } finally { cleanupTempFiles(); } } private File getEmptyFile() throws SVNException { if (this.emptyFile == null) { this.emptyFile = SVNFileUtil.createTempFile("", ""); } return this.emptyFile; } public SVNCommitInfo closeEdit() throws SVNException { return null; } public void abortEdit() throws SVNException { } public void applyTextDelta(String path, String baseChecksum) throws SVNException { FileBaton fb = fileBaton; if (fb.skip) { return; } if (!this.textDeltas) { fb.pathStartRevision = null; fb.pathEndRevision = null; return; } if (!fb.added) { getFileFromRa(fb, false); } else { fb.pathStartRevision = null; } if (baseChecksum != null && !fb.startMd5Checksum.equals(baseChecksum)) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CHECKSUM_MISMATCH, "Base checksum mismatch for ''{0}''", fb.path); SVNErrorManager.error(errorMessage, SVNLogType.WC); } if (fb.pathEndRevision == null) { File tmpDir = new File(System.getProperty("java.io.tmpdir")); fb.pathEndRevision = SVNFileUtil.createUniqueFile(tmpDir, "svn", "tmp", true); tempFiles.add(fb.pathEndRevision); } if (fb.pathStartRevision == null) { deltaProcessor.applyTextDelta(SVNFileUtil.DUMMY_IN, fb.pathEndRevision, true); } else { deltaProcessor.applyTextDelta(fb.pathStartRevision, fb.pathEndRevision, true); } } public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { if (!this.textDeltas) { return null; } return deltaProcessor.textDeltaChunk(diffWindow); } public void textDeltaEnd(String path) throws SVNException { if (!this.textDeltas) { return; } FileBaton fb = fileBaton; fb.resultMd5Checksum = deltaProcessor.textDeltaEnd(); } public void cleanup() { cleanupTempFiles(); if (emptyFile != null) { try { SVNFileUtil.deleteFile(emptyFile); } catch (SVNException e) { //ignore } emptyFile = null; } } public void cleanupTempFiles() { for (File tempFile : tempFiles) { try { SVNFileUtil.deleteFile(tempFile); } catch (SVNException e) { //ignore } } tempFiles.clear(); } private void diffDeletedFile(String path) throws SVNException { FileBaton fb = new FileBaton(path, dirBaton, false); boolean skip = false; SvnDiffSource leftSource = new SvnDiffSource(this.revision); result.reset(); callback.fileOpened(result, SVNFileUtil.createFilePath(path), leftSource, null, null, false, null); skip = result.skip; if (skip) { return; } getFileFromRa(fb, !textDeltas); result.reset(); callback.fileDeleted(result, SVNFileUtil.createFilePath(fb.path), leftSource, fb.pathStartRevision, fb.pristineProps); } private void diffDeletedDirectory(String path) throws SVNException { boolean skip = false; boolean skipChildren = false; SvnDiffSource leftSource = new SvnDiffSource(this.revision); DirBaton pb = dirBaton; DirBaton db = new DirBaton(path, pb, false, SVNRepository.INVALID_REVISION); assert SVNRevision.isValidRevisionNumber(this.revision); result.reset(); callback.dirOpened(result, SVNFileUtil.createFilePath(path), leftSource, null, null, null); skip = result.skip; skipChildren = result.skipChildren; SVNProperties leftProps = new SVNProperties(); List<SVNDirEntry> dirEntries = new ArrayList<SVNDirEntry>(); if (!skip || !skipChildren) { repository.getDir(path, this.revision, skip ? null : leftProps, skipChildren ? null : dirEntries); } if (!skipChildren) { for (SVNDirEntry dirEntry : dirEntries) { String name = dirEntry.getName(); String childPath = SVNPathUtil.append(path, name); if (dirEntry.getKind() == SVNNodeKind.FILE) { diffDeletedFile(childPath); } else if (dirEntry.getKind() == SVNNodeKind.DIR) { diffDeletedDirectory(childPath); } } } if (!skip) { result.reset(); callback.dirDeleted(result, SVNFileUtil.createFilePath(path), leftSource, leftProps, null); } } private SVNProperties removeNonPropChanges(SVNProperties pristineProps, SVNProperties changes) { SVNProperties newChanges = new SVNProperties(); for (Iterator<Map.Entry<String, SVNPropertyValue>> iterator = changes.asMap().entrySet().iterator(); iterator.hasNext(); ) { final Map.Entry<String, SVNPropertyValue> entry = iterator.next(); String name = entry.getKey(); SVNPropertyValue value = entry.getValue(); boolean remove = false; if (value != null) { SVNPropertyValue oldValue = pristineProps.getSVNPropertyValue(name); if (oldValue != null && oldValue.equals(value)) { remove = true; } } if (!remove) { newChanges.put(name, value); } } return newChanges; } private void getFileFromRa(FileBaton fb, boolean propsOnly) throws SVNException { if (fb.pristineProps == null) { fb.pristineProps = new SVNProperties(); } if (!propsOnly) { File tmpDir = new File(System.getProperty("java.io.tmpdir")); fb.pathStartRevision = SVNFileUtil.createUniqueFile(tmpDir, "svn", "tmp", true); tempFiles.add(fb.pathStartRevision); OutputStream outputStream = null; SVNChecksumOutputStream checksumOutputStream = null; try { outputStream = SVNFileUtil.openFileForWriting(fb.pathStartRevision); checksumOutputStream = new SVNChecksumOutputStream(outputStream, SVNChecksumOutputStream.MD5_ALGORITHM, false); repository.getFile(fb.path, fb.baseRevision, fb.pristineProps, checksumOutputStream); fb.startMd5Checksum = checksumOutputStream.getDigest(); } finally { SVNFileUtil.closeFile(checksumOutputStream); SVNFileUtil.closeFile(outputStream);//close original output stream because checksumOutputStream won't close it } } else { repository.getFile(fb.path, fb.baseRevision, fb.pristineProps, null); } } private static class DirBaton { private boolean added; private boolean treeConflicted; private boolean skip; private boolean skipChildren; private String path; private DirBaton parentBaton; private long baseRevision; private SvnDiffSource leftSource; private SvnDiffSource rightSource; public boolean hasPropChange; public SVNProperties propChanges; public DirBaton(String path, DirBaton parentBaton, boolean added, long baseRevision) { this.path = path; this.parentBaton = parentBaton; this.added = added; this.baseRevision = baseRevision; this.propChanges = new SVNProperties(); } } private class FileBaton { private boolean added; private boolean treeConflicted; private boolean skip; private String path; private File pathStartRevision; private SVNProperties pristineProps; private long baseRevision; private File pathEndRevision; private String startMd5Checksum; private String resultMd5Checksum; private SVNProperties propChanges; private boolean hasPropChanges; private SvnDiffSource leftSource; private SvnDiffSource rightSource; private DirBaton parentBaton; public FileBaton(String path, DirBaton parentBaton, boolean added) { this.path = path; this.parentBaton = parentBaton; this.added = added; this.propChanges = new SVNProperties(); this.baseRevision = SvnNgRemoteDiffEditor2.this.revision; } } }