/* * ==================================================================== * 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.OutputStream; import java.util.Collection; import java.util.Iterator; import java.util.Map; import org.tmatesoft.svn.core.ISVNDirEntryHandler; import org.tmatesoft.svn.core.SVNCommitInfo; import org.tmatesoft.svn.core.SVNErrorCode; 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.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNHashSet; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea; 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.SVNEvent; import org.tmatesoft.svn.core.wc.SVNEventAction; import org.tmatesoft.svn.core.wc.SVNStatusType; /** * @version 1.3 * @author TMate Software Ltd. */ public class SVNRemoteDiffEditor implements ISVNReusableEditor { protected SVNRepository myRepos; protected long myRevision1; protected long myRevision2; protected File myTarget; protected SVNAdminArea myAdminArea; protected boolean myIsDryRun; protected SVNDeltaProcessor myDeltaProcessor; protected ISVNEventHandler myEventHandler; protected ISVNEventHandler myCancelHandler; protected AbstractDiffCallback myDiffCallback; protected SVNDirectoryInfo myCurrentDirectory; protected SVNFileInfo myCurrentFile; protected File myTempDirectory; protected Collection myTempFiles; protected Map myDeletedPaths; private boolean myIsUseGlobalTmp; public SVNRemoteDiffEditor(SVNAdminArea adminArea, File target, AbstractDiffCallback callback, SVNRepository repos, long revision1, long revision2, boolean dryRun, ISVNEventHandler handler, ISVNEventHandler cancelHandler) { myAdminArea = adminArea; myTarget = target; myDiffCallback = callback; myRepos = repos; myRevision1 = revision1; myRevision2 = revision2; myEventHandler = handler; myCancelHandler = cancelHandler; myDeltaProcessor = new SVNDeltaProcessor(); myIsDryRun = dryRun; myDeletedPaths = new SVNHashMap(); } public void reset(long revision1, long revision2) { myRevision1 = revision1; myRevision2 = revision2; } public void targetRevision(long revision) throws SVNException { myRevision2 = revision; } public void openRoot(long revision) throws SVNException { myCurrentDirectory = new SVNDirectoryInfo(null, "", false); myCurrentDirectory.loadFromRepository(revision); } public void deleteEntry(String path, long revision) throws SVNException { if (myCurrentDirectory.myIsSkip || myCurrentDirectory.myIsTreeConflicted) { return; } SVNNodeKind nodeKind = myRepos.checkPath(path, myRevision1); SVNAdminArea dir = retrieve(myCurrentDirectory.myWCFile, true); deleteEntry(path, nodeKind, dir); } protected void deleteEntry(String path, SVNNodeKind nodeKind, SVNAdminArea dir) throws SVNException { SVNStatusType type = SVNStatusType.INAPPLICABLE; SVNEventAction action = SVNEventAction.SKIP; SVNEventAction expectedAction = SVNEventAction.UPDATE_DELETE; boolean[] isTreeConflicted = { false }; if (myAdminArea == null || dir != null) { if (nodeKind == SVNNodeKind.FILE) { SVNFileInfo file = new SVNFileInfo(path, false); file.loadFromRepository(myRevision1); String baseType = file.myBaseProperties.getStringValue(SVNProperty.MIME_TYPE); type = getDiffCallback().fileDeleted(path, file.myBaseFile, null, baseType, null, file.myBaseProperties, isTreeConflicted); } else if (nodeKind == SVNNodeKind.DIR) { type = getDiffCallback().directoryDeleted(path, isTreeConflicted); } if (type != SVNStatusType.MISSING && type != SVNStatusType.OBSTRUCTED && !isTreeConflicted[0]) { action = SVNEventAction.UPDATE_DELETE; if (myIsDryRun) { getDiffCallback().addDeletedPath(path); } } } action = isTreeConflicted[0] ? SVNEventAction.TREE_CONFLICT : action; addDeletedPath(path, nodeKind, type, action, expectedAction, isTreeConflicted[0]); } protected void addDeletedPath(String path, SVNNodeKind nodeKind, SVNStatusType type, SVNEventAction action, SVNEventAction expectedAction, boolean isTreeConflicted) { if (myEventHandler != null) { File deletedFile = new File(myTarget, path); KindActionState kas = new KindActionState(); kas.myAction = action; kas.myKind = nodeKind; kas.myStatus = type; kas.myExpectedAction = expectedAction; kas.myIsTreeConflicted = isTreeConflicted; myDeletedPaths.put(deletedFile, kas); } } public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException { SVNDirectoryInfo parentDir = myCurrentDirectory; myCurrentDirectory = new SVNDirectoryInfo(myCurrentDirectory, path, true); myCurrentDirectory.myBaseProperties = new SVNProperties(); if (parentDir.myIsSkip || parentDir.myIsTreeConflicted) { myCurrentDirectory.myIsSkip = true; return; } boolean[] isTreeConflicted = { false }; SVNStatusType type = getDiffCallback().directoryAdded(path, myRevision2, isTreeConflicted); myCurrentDirectory.myIsTreeConflicted = isTreeConflicted[0]; if (myEventHandler != null) { SVNEventAction action = SVNEventAction.UPDATE_ADD; SVNNodeKind kind = SVNNodeKind.DIR; KindActionState kas = (KindActionState) myDeletedPaths.get(myCurrentDirectory.myWCFile); if (kas != null) { kind = kas.myKind; type = kas.myStatus; myDeletedPaths.remove(myCurrentDirectory.myWCFile); } if (myCurrentDirectory.myIsTreeConflicted) { action = SVNEventAction.TREE_CONFLICT; } else if (kas != null) { if (kas.myAction == SVNEventAction.UPDATE_DELETE) { action = SVNEventAction.UPDATE_REPLACE; } else { action = kas.myAction; } } else if (type == SVNStatusType.MISSING || type == SVNStatusType.OBSTRUCTED) { action = SVNEventAction.SKIP; } SVNEvent event = SVNEventFactory.createSVNEvent(myCurrentDirectory.myWCFile, kind, null, SVNRepository.INVALID_REVISION, type, type, null, action, SVNEventAction.UPDATE_ADD, null, null); myEventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN); } } public void openDir(String path, long revision) throws SVNException { SVNDirectoryInfo parentDir = myCurrentDirectory; myCurrentDirectory = new SVNDirectoryInfo(myCurrentDirectory, path, false); if (parentDir.myIsSkip || parentDir.myIsTreeConflicted) { myCurrentDirectory.myIsSkip = true; return; } myCurrentDirectory.loadFromRepository(revision); boolean[] isTreeConflicted = { false }; getDiffCallback().directoryOpened(path, revision, isTreeConflicted); myCurrentDirectory.myIsTreeConflicted = isTreeConflicted[0]; } public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException { if (myCurrentDirectory.myIsSkip) { return; } myCurrentDirectory.myPropertyDiff.put(name, value); } public void closeDir() throws SVNException { if (myCurrentDirectory.myIsSkip) { myCurrentDirectory = myCurrentDirectory.myParent; return; } SVNStatusType type = SVNStatusType.UNKNOWN; SVNEventAction expectedAction = SVNEventAction.UPDATE_UPDATE; SVNEventAction action = expectedAction; if (myIsDryRun) { getDiffCallback().clearDeletedPaths(); } SVNAdminArea dir = null; if (!myCurrentDirectory.myPropertyDiff.isEmpty()) { try { dir = retrieve(myCurrentDirectory.myWCFile, myIsDryRun); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) { if (!myCurrentDirectory.myIsAdded && myEventHandler != null) { action = myCurrentDirectory.myIsTreeConflicted ? SVNEventAction.TREE_CONFLICT : SVNEventAction.SKIP; SVNEvent event = SVNEventFactory.createSVNEvent(myCurrentDirectory.myWCFile, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNStatusType.MISSING, SVNStatusType.MISSING, null, action, expectedAction, null, null); myEventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN); } myCurrentDirectory = myCurrentDirectory.myParent; return; } throw e; } if (!myIsDryRun || dir != null) { boolean[] isTreeConflicted = { false }; type = getDiffCallback().propertiesChanged(myCurrentDirectory.myRepositoryPath, myCurrentDirectory.myBaseProperties, myCurrentDirectory.myPropertyDiff, isTreeConflicted); if (isTreeConflicted[0]) { myCurrentDirectory.myIsTreeConflicted = true; } } } getDiffCallback().directoryClosed(myCurrentDirectory.myRepositoryPath, null); if (type == SVNStatusType.UNKNOWN) { action = SVNEventAction.UPDATE_NONE; } if (!myCurrentDirectory.myIsAdded && myEventHandler != null) { for (Iterator deletedPathsIter = myDeletedPaths.keySet().iterator(); deletedPathsIter.hasNext();) { File deletedPath = (File) deletedPathsIter.next(); KindActionState kas = (KindActionState) myDeletedPaths.get(deletedPath); SVNEvent event = SVNEventFactory.createSVNEvent(deletedPath, kas.myKind, null, SVNRepository.INVALID_REVISION, kas.myStatus, kas.myStatus, SVNStatusType.LOCK_INAPPLICABLE, kas.myAction, kas.myExpectedAction, null, null); myEventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN); deletedPathsIter.remove(); } action = myCurrentDirectory.myIsTreeConflicted ? SVNEventAction.TREE_CONFLICT : action; SVNEvent event = SVNEventFactory.createSVNEvent(myCurrentDirectory.myWCFile, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNStatusType.INAPPLICABLE, type, SVNStatusType.LOCK_INAPPLICABLE, action, expectedAction, null, null); myEventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN); } myCurrentDirectory = myCurrentDirectory.myParent; } public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { myCurrentFile = createFileInfo(path, true); if (myCurrentDirectory.myIsSkip || myCurrentDirectory.myIsTreeConflicted) { myCurrentFile.myIsSkip = true; return; } myCurrentFile.myBaseProperties = new SVNProperties(); myCurrentFile.myBaseFile = SVNFileUtil.createUniqueFile(getTempDirectory(), ".diff", ".tmp", myIsUseGlobalTmp); } public void openFile(String path, long revision) throws SVNException { myCurrentFile = createFileInfo(path, false); if (myCurrentDirectory.myIsSkip || myCurrentDirectory.myIsTreeConflicted) { myCurrentFile.myIsSkip = true; return; } myCurrentFile.loadFromRepository(revision); } public void changeFileProperty(String commitPath, String name, SVNPropertyValue value) throws SVNException { if (myCurrentFile.myIsSkip) { return; } myCurrentFile.myPropertyDiff.put(name, value); } public void applyTextDelta(String commitPath, String baseChecksum) throws SVNException { if (myCurrentFile.myIsSkip) { return; } SVNAdminArea dir = null; try { dir = retrieveParent(myCurrentFile.myWCFile, true); } catch (SVNException e) { dir = null; } myCurrentFile.myFile = createTempFile(dir, SVNPathUtil.tail(commitPath)); myDeltaProcessor.applyTextDelta(myCurrentFile.myBaseFile, myCurrentFile.myFile, false); } public OutputStream textDeltaChunk(String commitPath, SVNDiffWindow diffWindow) throws SVNException { if (myCurrentFile.myIsSkip) { return SVNFileUtil.DUMMY_OUT; } return myDeltaProcessor.textDeltaChunk(diffWindow); } public void textDeltaEnd(String commitPath) throws SVNException { if (myCurrentFile.myIsSkip) { return; } myDeltaProcessor.textDeltaEnd(); } public void closeFile(String commitPath, String textChecksum) throws SVNException { if (myCurrentFile.myIsSkip) { return; } boolean[] isTreeConflicted = { false }; closeFile(commitPath, myCurrentFile.myIsAdded, myCurrentFile.myWCFile, myCurrentFile.myFile, myCurrentFile.myPropertyDiff, myCurrentFile.myBaseProperties, myCurrentFile.myBaseFile, isTreeConflicted); myCurrentFile.myIsTreeConflicted = isTreeConflicted[0]; } protected void closeFile(String commitPath, boolean added, File wcFile, File file, SVNProperties propertyDiff, SVNProperties baseProperties, File baseFile, boolean[] isTreeConflicted) throws SVNException { SVNEventAction expectedAction = added ? SVNEventAction.UPDATE_ADD : SVNEventAction.UPDATE_UPDATE; SVNStatusType[] type = {SVNStatusType.UNKNOWN, SVNStatusType.UNKNOWN}; try { retrieveParent(wcFile, myIsDryRun); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) { if (myEventHandler != null) { SVNEvent event = SVNEventFactory.createSVNEvent(wcFile, SVNNodeKind.FILE, null, SVNRepository.INVALID_REVISION, SVNStatusType.MISSING, SVNStatusType.UNKNOWN, null, SVNEventAction.SKIP, expectedAction, null, null); myEventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN); } return; } throw e; } if (file != null || !propertyDiff.isEmpty()) { String baseMimeType = baseProperties.getStringValue(SVNProperty.MIME_TYPE); String mimeType = propertyDiff.getStringValue(SVNProperty.MIME_TYPE); if (added) { type = getDiffCallback().fileAdded(commitPath, file != null ? baseFile : null, file, 0, myRevision2, baseMimeType, mimeType, baseProperties, propertyDiff, isTreeConflicted); } else { type = getDiffCallback().fileChanged(commitPath, file != null ? baseFile : null, file, myRevision1, myRevision2, baseMimeType, mimeType, baseProperties, propertyDiff, isTreeConflicted); } } if (myEventHandler != null) { SVNNodeKind kind = SVNNodeKind.FILE; SVNEventAction action; KindActionState kas = (KindActionState) myDeletedPaths.get(wcFile); if (kas != null) { myDeletedPaths.remove(wcFile); kind = kas.myKind; type[0] = type[1] = kas.myStatus; } if (isTreeConflicted != null && isTreeConflicted.length > 0 && isTreeConflicted[0]) { action = SVNEventAction.TREE_CONFLICT; } else if (kas != null) { if (kas.myAction == SVNEventAction.UPDATE_DELETE && added) { action = SVNEventAction.UPDATE_REPLACE; } else { action = kas.myAction; } } else if (type[0] == SVNStatusType.MISSING || type[0] == SVNStatusType.OBSTRUCTED) { action = SVNEventAction.SKIP; } else if (added) { action = SVNEventAction.UPDATE_ADD; } else { action = SVNEventAction.UPDATE_UPDATE; } SVNEvent event = SVNEventFactory.createSVNEvent(wcFile, kind, null, SVNRepository.INVALID_REVISION, type[0], type[1], null, action, expectedAction, null, null); myEventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN); } } public SVNCommitInfo closeEdit() throws SVNException { return null; } public void abortEdit() throws SVNException { } public void absentDir(String path) throws SVNException { if (myCurrentDirectory.myWCFile != null) { File dir = new File(myCurrentDirectory.myWCFile, SVNPathUtil.tail(path)); SVNEvent event = SVNEventFactory.createSVNEvent(dir, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNStatusType.MISSING, SVNStatusType.MISSING, SVNStatusType.MISSING, SVNEventAction.SKIP, null, null, null); myEventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN); } } public void absentFile(String path) throws SVNException { if (myCurrentDirectory.myWCFile != null) { File file = new File(myCurrentDirectory.myWCFile, SVNPathUtil.tail(path)); SVNEvent event = SVNEventFactory.createSVNEvent(file, SVNNodeKind.FILE, null, SVNRepository.INVALID_REVISION, SVNStatusType.MISSING, SVNStatusType.MISSING, SVNStatusType.MISSING, SVNEventAction.SKIP, null, null, null); myEventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN); } } public void cleanup() throws SVNException { if (myTempDirectory != null) { SVNFileUtil.deleteAll(myTempDirectory, true); myTempDirectory = null; } if (myTempFiles != null) { for(Iterator files = myTempFiles.iterator(); files.hasNext();) { SVNFileUtil.deleteFile((File) files.next()); } } } protected SVNAdminArea retrieve(File path, boolean lenient) throws SVNException { if (myAdminArea == null) { return null; } try { return myAdminArea.getWCAccess().retrieve(path); } catch (SVNException e) { if (lenient) { return null; } throw e; } } protected SVNAdminArea retrieveParent(File path, boolean lenient) throws SVNException { if (myAdminArea == null) { return null; } return retrieve(path.getParentFile(), lenient); } protected AbstractDiffCallback getDiffCallback() { return myDiffCallback; } protected File getTempDirectory() throws SVNException { if (myTempDirectory == null) { myTempDirectory = getDiffCallback().createTempDirectory(); } return myTempDirectory; } protected File createTempFile(SVNAdminArea dir, String name) throws SVNException { if (dir != null && dir.isLocked()) { File tmpFile = dir.getBaseFile(name, true); if (myTempFiles == null) { myTempFiles = new SVNHashSet(); } myTempFiles.add(tmpFile); return tmpFile; } return SVNFileUtil.createUniqueFile(getTempDirectory(), ".diff", ".tmp", myIsUseGlobalTmp); } protected SVNFileInfo createFileInfo(String path, boolean added) { return new SVNFileInfo(path, added); } protected void setIsConflicted(boolean[] isConflictedResult, boolean isConflicted) { if (isConflictedResult != null && isConflictedResult.length > 0) { isConflictedResult[0] = isConflicted; } } public void setUseGlobalTmp(boolean global) { myIsUseGlobalTmp = global; } protected class SVNDirectoryInfo { public SVNDirectoryInfo(SVNDirectoryInfo parent, String path, boolean added) { myParent = parent; myRepositoryPath = path; myWCFile = myTarget != null ? new File(myTarget, path) : null; myIsAdded = added; myPropertyDiff = new SVNProperties(); } public void loadFromRepository(long baseRevision) throws SVNException { myBaseProperties = new SVNProperties(); myRepos.getDir(myRepositoryPath, baseRevision, myBaseProperties, (ISVNDirEntryHandler) null); } protected boolean myIsAdded; protected boolean myIsSkip; protected boolean myIsTreeConflicted; protected String myRepositoryPath; protected File myWCFile; protected SVNProperties myBaseProperties; protected SVNProperties myPropertyDiff; protected SVNDirectoryInfo myParent; } protected class SVNFileInfo { public SVNFileInfo(String path, boolean added) { myRepositoryPath = path; myIsAdded = added; myWCFile = myTarget != null ? new File(myTarget, path) : null; myPropertyDiff = new SVNProperties(); } public void loadFromRepository(long revision) throws SVNException { myBaseFile = SVNFileUtil.createUniqueFile(getTempDirectory(), ".diff", ".tmp", myIsUseGlobalTmp); OutputStream os = null; myBaseProperties = new SVNProperties(); try { os = SVNFileUtil.openFileForWriting(myBaseFile); myRepos.getFile(myRepositoryPath, revision, myBaseProperties, new SVNCancellableOutputStream(os, myCancelHandler)); } finally { SVNFileUtil.closeFile(os); } } protected String myRepositoryPath; protected File myWCFile; protected boolean myIsAdded; protected boolean myIsSkip; protected boolean myIsTreeConflicted; protected File myFile; protected File myBaseFile; protected SVNProperties myBaseProperties; protected SVNProperties myPropertyDiff; } protected class KindActionState { protected SVNNodeKind myKind; protected SVNEventAction myAction; protected SVNEventAction myExpectedAction; protected SVNStatusType myStatus; protected boolean myIsTreeConflicted; } }