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.wc17.SVNWCContext; 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.ISVNEventHandler; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc2.SvnChecksum; import org.tmatesoft.svn.core.wc2.SvnMergeResult; import org.tmatesoft.svn.util.SVNLogType; import java.io.File; import java.io.OutputStream; import java.util.*; public class SvnNgRemoteMergeEditor implements ISVNEditor { private final ISvnDiffCallback2 processor; private long targetRevision; private long revision; private SvnDiffCallbackResult mergeResult; private DirectoryBaton currentDirectory; private FileBaton currentFile; private SVNRepository repository; private boolean textDeltas; private final Collection<File> tmpFiles; private File emptyFile; private boolean pureRemoteDiff; private File globalTmpDir; private SVNWCContext context; private File target; public SvnNgRemoteMergeEditor(File target, SVNWCContext context, SVNRepository repository, long revision, ISvnDiffCallback2 processor, boolean textDeltas) { this.target = target; this.context = context; this.repository = repository; this.revision = revision; this.processor = processor; this.textDeltas = textDeltas; this.mergeResult = new SvnDiffCallbackResult(); this.tmpFiles = new HashSet<File>(); this.pureRemoteDiff = target == null; } public void targetRevision(long revision) throws SVNException { this.targetRevision = revision; } public void openRoot(long revision) throws SVNException { DirectoryBaton db = new DirectoryBaton("", null, false, revision); db.leftSource = new SvnDiffSource(this.revision); db.rightSource = new SvnDiffSource(this.targetRevision); mergeResult.reset(); processor.dirOpened(mergeResult, SVNFileUtil.createFilePath(""), db.leftSource, db.rightSource, null, null); db.skip = mergeResult.skip; db.skipChildren = mergeResult.skipChildren; db.pdb = mergeResult.newBaton; this.currentDirectory = db; } private void diffDeletedFile(String path, DirectoryBaton db) throws SVNException { FileBaton fb = new FileBaton(path, db, false); boolean skip = false; SvnDiffSource leftSource = new SvnDiffSource(this.revision); checkCancelled(); mergeResult.reset(); processor.fileOpened(mergeResult, SVNFileUtil.createFilePath(path), leftSource, null, null, false, db.pdb); skip = mergeResult.skip; checkCancelled(); if (skip) { return; } getFileFromRepository(fb, !this.textDeltas); mergeResult.reset(); processor.fileDeleted(mergeResult, SVNFileUtil.createFilePath(fb.path), leftSource, fb.pathStartRevision, fb.pristineProps); } private void diffDeletedDirectory(String path, DirectoryBaton parentBaton) throws SVNException { SvnDiffSource leftSource = new SvnDiffSource(this.revision); DirectoryBaton db = new DirectoryBaton(path, parentBaton, false, -1); assert SVNRevision.isValidRevisionNumber(this.revision); checkCancelled(); mergeResult.reset(); processor.dirOpened(mergeResult, SVNFileUtil.createFilePath(path), leftSource, null, null, parentBaton.pdb); boolean skip = mergeResult.skip; boolean skipChildren = mergeResult.skipChildren; db.pdb = mergeResult.newBaton; SVNProperties leftProps = null; List<SVNDirEntry> dirEntries = null; if (!skip || !skipChildren) { dirEntries = skipChildren ? null : new ArrayList<SVNDirEntry>(); leftProps = skip ? null : new SVNProperties(); repository.getDir(path, this.revision, leftProps, SVNDirEntry.DIRENT_KIND, dirEntries); } if (!skipChildren) { for (SVNDirEntry dirEntry : dirEntries) { String name = dirEntry.getName(); String childPath = SVNPathUtil.append(path, name); if (dirEntry.getKind() == SVNNodeKind.FILE) { diffDeletedFile(childPath, db); } else if (dirEntry.getKind() == SVNNodeKind.DIR) { diffDeletedDirectory(childPath, db); } } } if (!skip) { mergeResult.reset(); processor.dirDeleted(mergeResult, SVNFileUtil.createFilePath(path), leftSource, leftProps, db.pdb); } } private void checkCancelled() throws SVNCancelException { ISVNEventHandler eventHandler = context.getEventHandler(); if (eventHandler != null) { eventHandler.checkCancelled(); } } public void deleteEntry(String path, long revision) throws SVNException { DirectoryBaton parentBaton = currentDirectory; if (parentBaton.skipChildren) { return; } SVNNodeKind kind = repository.checkPath(path, this.revision); if (kind == SVNNodeKind.FILE) { diffDeletedFile(path, parentBaton); } else if (kind == SVNNodeKind.DIR) { diffDeletedDirectory(path, parentBaton); } } public void absentDir(String path) throws SVNException { mergeResult.reset(); processor.nodeAbsent(mergeResult, SVNFileUtil.createFilePath(path), currentDirectory.pdb); } public void absentFile(String path) throws SVNException { mergeResult.reset(); processor.nodeAbsent(mergeResult, SVNFileUtil.createFilePath(path), currentDirectory.pdb); } public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException { DirectoryBaton pb = currentDirectory; DirectoryBaton db = new DirectoryBaton(path, pb, true, -1); currentDirectory = db; if (pb.skipChildren) { db.skip = true; db.skipChildren = true; return; } db.rightSource = new SvnDiffSource(this.targetRevision); mergeResult.reset(); processor.dirOpened(mergeResult, SVNFileUtil.createFilePath(db.path), null, db.rightSource, null, pb.pdb); db.skip = mergeResult.skip; db.skipChildren = mergeResult.skipChildren; db.pdb = mergeResult.newBaton; } public void openDir(String path, long revision) throws SVNException { DirectoryBaton pb = currentDirectory; DirectoryBaton db = new DirectoryBaton(path, pb, false, revision); currentDirectory = db; if (pb.skipChildren) { db.skip = true; db.skipChildren = true; return; } db.leftSource = new SvnDiffSource(this.revision); db.rightSource = new SvnDiffSource(this.targetRevision); mergeResult.reset(); processor.dirOpened(mergeResult, SVNFileUtil.createFilePath(path), db.leftSource, db.rightSource, null, pb != null ? pb.pdb : null); db.skip = mergeResult.skip; db.skipChildren = mergeResult.skipChildren; db.pdb = mergeResult.newBaton; } public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException { DirectoryBaton db = currentDirectory; if (db.skip) { return; } if (SVNProperty.isWorkingCopyProperty(name)) { return; } else if (SVNProperty.isRegularProperty(name)) { db.hasPropChange = true; } if (db.propChanges == null) { db.propChanges = new SVNProperties(); } db.propChanges.put(name, value); } public void closeDir() throws SVNException { DirectoryBaton db = currentDirectory; boolean sendChanged = false; SVNProperties pristineProps; if ((db.hasPropChange || db.added) && !db.skip) { if (db.added) { pristineProps = new SVNProperties(); } else { pristineProps = new SVNProperties(); repository.getDir(db.path, db.baseRevision, pristineProps, 0, (Collection)null); } if (db.propChanges.size() > 0) { removeNonPropChanges(pristineProps, db.propChanges); } if (db.propChanges.size() > 0 || db.added) { SVNProperties rightProps = new SVNProperties(pristineProps); rightProps.putAll(db.propChanges); rightProps.removeNullValues(); if (db.added) { mergeResult.reset(); processor.dirAdded(mergeResult, SVNFileUtil.createFilePath(db.path), null, db.rightSource, null, rightProps, db.pdb); } else { mergeResult.reset(); processor.dirChanged(mergeResult, SVNFileUtil.createFilePath(db.path), db.leftSource, db.rightSource, pristineProps, rightProps, db.propChanges, db.pdb); } sendChanged = true; } } if (!db.skip && !sendChanged) { mergeResult.reset(); processor.dirClosed(mergeResult, SVNFileUtil.createFilePath(db.path), db.leftSource, db.rightSource, db.pdb); } currentDirectory = currentDirectory.parentBaton; } public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { DirectoryBaton pb = currentDirectory; FileBaton fb = new FileBaton(path, pb, true); currentFile = fb; if (pb.skipChildren) { fb.skip = true; return; } fb.pristineProps = new SVNProperties(); fb.rightSource = new SvnDiffSource(this.targetRevision); mergeResult.reset(); processor.fileOpened(mergeResult, SVNFileUtil.createFilePath(path), null, fb.rightSource, null, false, pb.pdb); fb.skip = mergeResult.skip; } public void openFile(String path, long revision) throws SVNException { DirectoryBaton pb = currentDirectory; FileBaton fb = new FileBaton(path, pb, false); currentFile = fb; if (pb.skipChildren) { fb.skip = true; return; } fb.baseRevision = revision; fb.leftSource = new SvnDiffSource(this.revision); fb.rightSource = new SvnDiffSource(this.targetRevision); mergeResult.reset(); processor.fileOpened(mergeResult, SVNFileUtil.createFilePath(path), fb.leftSource, fb.rightSource, null, false, pb.pdb); fb.skip = mergeResult.skip; } public void changeFileProperty(String path, String propertyName, SVNPropertyValue propertyValue) throws SVNException { FileBaton fb = currentFile; if (fb.skip) { return; } if (SVNProperty.isWorkingCopyProperty(propertyName)) { return; } else if (SVNProperty.isRegularProperty(propertyName)) { fb.hasPropChange = true; } if (fb.propChanges == null) { fb.propChanges = new SVNProperties(); } fb.propChanges.put(propertyName, propertyValue); } public void closeFile(String path, String expectedChecksum) throws SVNException { FileBaton fb = currentFile; if (fb.skip) { return; } if (expectedChecksum != null && textDeltas) { if (fb.resultChecksum != null && !expectedChecksum.equals(fb.resultChecksum)) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CHECKSUM_MISMATCH, "Checksum mismatch for ''{0}''", fb.path); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } if (fb.added || fb.pathEndRevision != null || fb.hasPropChange) { if (!fb.added && fb.pristineProps == null) { getFileFromRepository(fb, true); } if (fb.pristineProps != null) { removeNonPropChanges(fb.pristineProps, fb.propChanges); } SVNProperties rightProps = fb.pristineProps == null ? new SVNProperties() : new SVNProperties(fb.pristineProps); rightProps.putAll(fb.propChanges); rightProps.removeNullValues(); if (fb.added) { mergeResult.reset(); processor.fileAdded(mergeResult, SVNFileUtil.createFilePath(fb.path), null, fb.rightSource, null, fb.pathEndRevision, null, rightProps); } else { mergeResult.reset(); boolean contentChanged = fb.baseChecksum== null || !fb.baseChecksum.equals(expectedChecksum); File leftFile = fb.pathEndRevision != null && contentChanged ? fb.pathStartRevision : null; File pathEndRevision = contentChanged ? fb.pathEndRevision : null; processor.fileChanged(mergeResult, SVNFileUtil.createFilePath(fb.path), fb.leftSource, fb.rightSource, leftFile, pathEndRevision, fb.pristineProps, rightProps, fb.pathEndRevision != null, fb.propChanges); } } currentFile = null; } public SVNCommitInfo closeEdit() throws SVNException { cleanup(); return null; } public void abortEdit() throws SVNException { } public void applyTextDelta(String path, String baseChecksum) throws SVNException { if (currentFile.skip) { return; } currentFile.deltaProcessor = new SVNDeltaProcessor(); if (!currentFile.added) { getFileFromRepository(currentFile, false); } else { currentFile.pathStartRevision = getEmptyFile(); } currentFile.pathEndRevision = createUniqueFile(SVNPathUtil.tail(path)); tmpFiles.add(currentFile.pathEndRevision); currentFile.baseChecksum = baseChecksum; currentFile.deltaProcessor.applyTextDelta(currentFile.pathStartRevision, currentFile.pathEndRevision, true); } private File getEmptyFile() throws SVNException { if (emptyFile == null) { emptyFile = createUniqueFile("empty"); tmpFiles.add(emptyFile); } return emptyFile; } private File createUniqueFile(String name) throws SVNException { File tmpDir = pureRemoteDiff ? getGlobalTmpDir() : context.getDb().getWCRootTempDir(target); return SVNFileUtil.createUniqueFile(tmpDir, name, ".tmp", false); } public File getGlobalTmpDir() throws SVNException { if (globalTmpDir == null) { globalTmpDir = SVNFileUtil.createTempDirectory("svndiff"); } return globalTmpDir; } public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { if (currentFile.deltaProcessor != null) { return currentFile.deltaProcessor.textDeltaChunk(diffWindow); } return SVNFileUtil.DUMMY_OUT; } public void textDeltaEnd(String path) throws SVNException { if (currentFile.deltaProcessor != null) { String checksum = currentFile.deltaProcessor.textDeltaEnd(); currentFile.resultChecksum = checksum; } } private void getFileFromRepository(FileBaton fileBaton, boolean propsOnly) throws SVNException { if (!propsOnly) { fileBaton.pathStartRevision = createUniqueFile(SVNPathUtil.tail(fileBaton.path)); OutputStream outputStream = SVNFileUtil.openFileForWriting(fileBaton.pathStartRevision); try { fileBaton.pristineProps = new SVNProperties(); repository.getFile(fileBaton.path, fileBaton.baseRevision, fileBaton.pristineProps, outputStream); } finally { SVNFileUtil.closeFile(outputStream); } } else { fileBaton.pristineProps = new SVNProperties(); repository.getFile(fileBaton.path, fileBaton.baseRevision, fileBaton.pristineProps, null); } } private void removeNonPropChanges(SVNProperties pristineProps, SVNProperties propChanges) { for (Iterator<String> iterator = propChanges.nameSet().iterator(); iterator.hasNext(); ) { final String propName = iterator.next(); SVNPropertyValue propertyValue = propChanges.getSVNPropertyValue(propName); if (propertyValue != null) { SVNPropertyValue oldValue = pristineProps.getSVNPropertyValue(propName); if (oldValue != null && oldValue.equals(propertyValue)) { iterator.remove(); } } } } public void cleanup() { for (File tmpFile : tmpFiles) { try { SVNFileUtil.deleteFile(tmpFile); } catch (SVNException ignore) { } } if (globalTmpDir != null) { SVNFileUtil.deleteAll(globalTmpDir, true); } } private class DirectoryBaton { private String path; private boolean added; private boolean treeConflicted; private boolean skip; private boolean skipChildren; private DirectoryBaton parentBaton; private SVNProperties propChanges; private boolean hasPropChange; private SvnDiffSource leftSource; private SvnDiffSource rightSource; private long baseRevision; private Object pdb; private DirectoryBaton(String path, DirectoryBaton parentBaton, boolean added, long baseRevision) { this.path = path; this.parentBaton = parentBaton; this.added = added; this.baseRevision = baseRevision; this.skip = false; this.skipChildren = false; this.propChanges = new SVNProperties(); this.baseRevision = revision; } } private class FileBaton { private String path; private DirectoryBaton parentBaton; private boolean added; private boolean treeConflicted; private boolean skip; private File pathStartRevision; private File pathEndRevision; private SVNProperties pristineProps; private long baseRevision; private SvnDiffSource leftSource; private SvnDiffSource rightSource; public boolean hasPropChange; public SVNProperties propChanges; public String baseChecksum; public String resultChecksum; public SVNDeltaProcessor deltaProcessor; private FileBaton(String path, DirectoryBaton parentBaton, boolean added) { this.path = path; this.parentBaton = parentBaton; this.added = added; this.baseRevision = revision; } } }