/* * ==================================================================== * 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.admin; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNDate; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc.SVNTreeConflictUtil; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription; import org.tmatesoft.svn.util.SVNLogType; import java.io.*; import java.util.Date; import java.util.Iterator; import java.util.Map; /** * @version 1.3 * @author TMate Software Ltd. */ public class SVNLogRunner { private boolean myIsEntriesChanged; private boolean myIsWCPropertiesChanged; private Map myTreeConflicts; private boolean myHasAddedTreeConflicts; private int myLogCount; private boolean myIsRerun; public SVNLogRunner(boolean rerun) { myIsRerun = rerun; } private Map getTreeConflicts() { if (myTreeConflicts == null) { myTreeConflicts = new SVNHashMap(); } return myTreeConflicts; } public void runCommand(SVNAdminArea adminArea, String name, SVNProperties attributes, int count) throws SVNException { SVNException error = null; String fileName = attributes.getStringValue(SVNLog.NAME_ATTR); if (SVNLog.DELETE_ENTRY.equals(name)) { File path = adminArea.getFile(fileName); SVNAdminArea dir = adminArea.getWCAccess().probeRetrieve(path); SVNEntry entry = dir.getWCAccess().getEntry(path, false); if (entry == null) { return; } try { if (entry.isDirectory()) { try { SVNAdminArea childDir = dir.getWCAccess().retrieve(path); // it should be null when there is no dir already. if (childDir != null) { childDir.extendLockToTree(); childDir.removeFromRevisionControl(childDir.getThisDirName(), true, false); } else { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.WC_NOT_LOCKED), SVNLogType.DEFAULT); } } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) { if (!entry.isScheduledForAddition()) { adminArea.deleteEntry(fileName); adminArea.saveEntries(false); } } else { throw e; } } } else if (entry.isFile()) { adminArea.removeFromRevisionControl(fileName, true, false); } } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_LEFT_LOCAL_MOD) { error = e; } } } else if (SVNLog.MODIFY_ENTRY.equals(name)) { try { Map entryAttrs = new SVNHashMap(); for (Iterator attrtibutesIter = attributes.nameSet().iterator(); attrtibutesIter.hasNext();) { String attrName = (String) attrtibutesIter.next(); if ("".equals(attrName) || SVNLog.NAME_ATTR.equals(attrName) || SVNLog.FORCE_ATTR.equals(attrName)) { continue; } String value = attributes.getStringValue(attrName); attrName = SVNProperty.SVN_ENTRY_PREFIX + attrName; entryAttrs.put(attrName, value); } if (entryAttrs.containsKey(SVNProperty.TEXT_TIME)) { String value = (String) entryAttrs.get(SVNProperty.TEXT_TIME); if (SVNLog.WC_TIMESTAMP.equals(value)) { File file = adminArea.getFile(fileName); value = SVNDate.formatDate(new Date(SVNFileUtil.getFileLastModified(file))); entryAttrs.put(SVNProperty.TEXT_TIME, value); } } if (entryAttrs.containsKey(SVNProperty.PROP_TIME)) { String value = (String) entryAttrs.get(SVNProperty.PROP_TIME); if (SVNLog.WC_TIMESTAMP.equals(value)) { SVNEntry entry = adminArea.getEntry(fileName, false); if (entry == null) { return; } value = adminArea.getPropertyTime(fileName); entryAttrs.put(SVNProperty.PROP_TIME, value); } } if (entryAttrs.containsKey(SVNProperty.WORKING_SIZE)) { String workingSize = (String) entryAttrs.get(SVNProperty.WORKING_SIZE); if (SVNLog.WC_WORKING_SIZE.equals(workingSize)) { SVNEntry entry = adminArea.getEntry(fileName, false); if (entry == null) { return; } File file = adminArea.getFile(fileName); if (!file.exists()) { entryAttrs.put(SVNProperty.WORKING_SIZE, "0"); } else { try { entryAttrs.put(SVNProperty.WORKING_SIZE, Long.toString(SVNFileUtil.getFileLength(file))); } catch (SecurityException se) { SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; SVNErrorMessage err = SVNErrorMessage.create(code, "Error getting file size on ''{0}''", file); SVNErrorManager.error(err, se, SVNLogType.WC); } } } } boolean force = false; if (attributes.containsName(SVNLog.FORCE_ATTR)) { String forceAttr = attributes.getStringValue(SVNLog.FORCE_ATTR); force = SVNProperty.booleanValue(forceAttr); } if (myIsRerun && adminArea.getEntry(fileName, true) == null) { // skip modification without an error. } else { try { adminArea.modifyEntry(fileName, entryAttrs, false, force); } catch (SVNException svne) { SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; SVNErrorMessage err = SVNErrorMessage.create(code, "Error modifying entry for ''{0}''", fileName); SVNErrorManager.error(err, svne, SVNLogType.WC); } setEntriesChanged(true); } } catch (SVNException svne) { error = svne; } } else if (SVNLog.MODIFY_WC_PROPERTY.equals(name)) { try { SVNVersionedProperties wcprops = adminArea.getWCProperties(fileName); if (wcprops != null) { String propName = attributes.getStringValue(SVNLog.PROPERTY_NAME_ATTR); SVNPropertyValue propValue = attributes.getSVNPropertyValue(SVNLog.PROPERTY_VALUE_ATTR); wcprops.setPropertyValue(propName, propValue); setWCPropertiesChanged(true); } } catch (SVNException svne) { error = svne; } } else if (SVNLog.DELETE_LOCK.equals(name)) { try { SVNEntry entry = adminArea.getEntry(fileName, true); if (entry != null) { entry.setLockToken(null); entry.setLockOwner(null); entry.setLockCreationDate(null); entry.setLockComment(null); setEntriesChanged(true); } } catch (SVNException svne) { SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; SVNErrorMessage err = SVNErrorMessage.create(code, "Error removing lock from entry for ''{0}''", fileName); error = new SVNException(err, svne); } } else if (SVNLog.DELETE_CHANGELIST.equals(name)) { try { Map entryAttrs = new SVNHashMap(); entryAttrs.put(SVNProperty.CHANGELIST, null); adminArea.modifyEntry(fileName, entryAttrs, false, false); setEntriesChanged(true); } catch (SVNException svne) { SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; SVNErrorMessage err = SVNErrorMessage.create(code, "Error removing changelist from entry ''{0}''", fileName); error = new SVNException(err, svne); } } else if (SVNLog.DELETE.equals(name)) { File file = adminArea.getFile(fileName); SVNFileUtil.deleteFile(file); } else if (SVNLog.READONLY.equals(name)) { File file = adminArea.getFile(fileName); SVNFileUtil.setReadonly(file, true); } else if (SVNLog.MOVE.equals(name)) { File src = adminArea.getFile(fileName); File dst = adminArea.getFile(attributes.getStringValue(SVNLog.DEST_ATTR)); try { SVNFileUtil.rename(src, dst); } catch (SVNException svne) { if (!myIsRerun || src.exists()) { error = new SVNException(svne.getErrorMessage().wrap("Can't move source to dest"), svne); } } } else if (SVNLog.APPEND.equals(name)) { File src = adminArea.getFile(fileName); File dst = adminArea.getFile(attributes.getStringValue(SVNLog.DEST_ATTR)); OutputStream os = null; InputStream is = null; try { os = SVNFileUtil.openFileForWriting(dst, true); is = SVNFileUtil.openFileForReading(src, SVNLogType.WC); while (true) { int r = is.read(); if (r < 0) { break; } os.write(r); } } catch (IOException e) { if (!myIsRerun || !(e instanceof FileNotFoundException)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot write to ''{0}'': {1}", new Object[] {dst, e.getLocalizedMessage()}); error = new SVNException(err, e); } } catch (SVNException svne) { if (!myIsRerun || src.exists()) { error = svne; } } finally { SVNFileUtil.closeFile(os); SVNFileUtil.closeFile(is); } } else if (SVNLog.SET_TIMESTAMP.equals(name)) { File file = adminArea.getFile(fileName); String timestamp = attributes.getStringValue(SVNLog.TIMESTAMP_ATTR); try { if (timestamp == null) { SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; SVNErrorMessage err = SVNErrorMessage.create(code, "Missing 'timestamp' attribute in ''{0}''", adminArea.getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } Date time = SVNDate.parseDate(timestamp); //TODO: what about special files (do not set for them). if (!SVNFileUtil.setLastModified(file, time.getTime())) { if (!file.canWrite() && file.isFile()) { SVNFileUtil.setReadonly(file, false); SVNFileUtil.setLastModified(file, time.getTime()); SVNFileUtil.setReadonly(file, true); } } } catch (SVNException svne) { error = svne; } } else if (SVNLog.UPGRADE_FORMAT.equals(name)) { String format = attributes.getStringValue(SVNLog.FORMAT_ATTR); SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; try { if (format == null) { SVNErrorMessage err = SVNErrorMessage.create(code, "Invalid 'format' attribute"); SVNErrorManager.error(err, SVNLogType.WC); } int number = -1; try { number = Integer.parseInt(format); } catch (NumberFormatException e) { SVNErrorMessage err = SVNErrorMessage.create(code, "Invalid 'format' attribute"); SVNErrorManager.error(err, e, SVNLogType.WC); } if (number == 0) { SVNErrorMessage err = SVNErrorMessage.create(code, "Invalid 'format' attribute"); SVNErrorManager.error(err, SVNLogType.WC); } adminArea.postUpgradeFormat(number); setEntriesChanged(true); } catch (SVNException svne) { error = svne; } } else if (SVNLog.MAYBE_READONLY.equals(name)) { File file = adminArea.getFile(fileName); try { SVNEntry entry = adminArea.getEntry(fileName, false); if (entry != null) { adminArea.closeVersionedProperties(); SVNVersionedProperties props = adminArea.getProperties(fileName); String needsLock = props.getStringPropertyValue(SVNProperty.NEEDS_LOCK); if (entry.getLockToken() == null && needsLock != null) { SVNFileUtil.setReadonly(file, true); } } } catch (SVNException svne) { error = svne; } } else if (SVNLog.MAYBE_EXECUTABLE.equals(name)) { adminArea.closeVersionedProperties(); SVNVersionedProperties props = adminArea.getProperties(fileName); boolean executable = SVNFileUtil.isWindows ? false : props.getPropertyValue(SVNProperty.EXECUTABLE) != null; if (executable) { SVNFileUtil.setExecutable(adminArea.getFile(fileName), true); } } else if (SVNLog.COPY_AND_TRANSLATE.equals(name)) { String dstName = attributes.getStringValue(SVNLog.DEST_ATTR); String versionedName = attributes.getStringValue(SVNLog.ATTR2); if (versionedName == null) { versionedName = dstName; } File src = adminArea.getFile(fileName); File dst = adminArea.getFile(dstName); //when performing a merge from a log runner we may have just set //new properties (log command that copies a new base prop file), //but probably we've got a non empty props cache which is no more //valid, so clean it up. adminArea.closeVersionedProperties(); try { try { SVNTranslator.translate(adminArea, versionedName, src, dst, null, true); } catch (SVNException svne) { if (!myIsRerun || src.exists()) { throw svne; } } // get properties for this entry. SVNVersionedProperties props = adminArea.getProperties(dstName); boolean executable = SVNFileUtil.isWindows ? false : props.getPropertyValue(SVNProperty.EXECUTABLE) != null; if (executable) { SVNFileUtil.setExecutable(dst, true); } SVNEntry entry = adminArea.getEntry(dstName, false); if (entry != null && entry.getLockToken() == null && props.getPropertyValue(SVNProperty.NEEDS_LOCK) != null) { SVNFileUtil.setReadonly(dst, true); } } catch (SVNException svne) { error = svne; } } else if (SVNLog.COPY_AND_DETRANSLATE.equals(name)) { String dstName = attributes.getStringValue(SVNLog.DEST_ATTR); String versionedName = attributes.getStringValue(SVNLog.ATTR2); adminArea.closeVersionedProperties(); try { SVNTranslator.translate(adminArea, versionedName != null ? versionedName : fileName, fileName, dstName, false); } catch (SVNException svne) { error = svne; } } else if (SVNLog.COPY.equals(name)) { File src = adminArea.getFile(fileName); File dst = adminArea.getFile(attributes.getStringValue(SVNLog.DEST_ATTR)); try { SVNFileUtil.copy(src, dst, true, false); } catch (SVNException svne) { error = svne; } } else if (SVNLog.ADD_TREE_CONFLICT.equals(name)) { File dirPath = adminArea.getRoot(); String conflictData = attributes.getStringValue(SVNLog.DATA_ATTR); Map newConflicts = SVNTreeConflictUtil.readTreeConflicts(dirPath, conflictData); Object[] conflictArray = newConflicts.values().toArray(); SVNTreeConflictDescription newConflict = (SVNTreeConflictDescription) conflictArray[0]; if (!getTreeConflicts().containsKey(newConflict.getPath())) { getTreeConflicts().put(newConflict.getPath(), newConflict); setTreeConflictsAdded(true); } } else if (SVNLog.MERGE.equals(name)) { File target = adminArea.getFile(fileName); try { SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; String leftPath = attributes.getStringValue(SVNLog.ATTR1); if (leftPath == null) { SVNErrorMessage err = SVNErrorMessage.create(code, "Missing 'left' attribute in ''{0}''", adminArea.getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } String rightPath = attributes.getStringValue(SVNLog.ATTR2); if (rightPath == null) { SVNErrorMessage err = SVNErrorMessage.create(code, "Missing 'right' attribute in ''{0}''", adminArea.getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } String leftLabel = attributes.getStringValue(SVNLog.ATTR3); leftLabel = leftLabel == null ? ".old" : leftLabel; String rightLabel = attributes.getStringValue(SVNLog.ATTR4); rightLabel = rightLabel == null ? ".new" : rightLabel; String targetLabel = attributes.getStringValue(SVNLog.ATTR5); targetLabel = targetLabel == null ? ".working" : targetLabel; //when performing a merge from a log runner we may have just set //new properties (log command that copies a new base prop file), //but probably we've got a non empty props cache which is no more //valid, so clean it up. adminArea.closeVersionedProperties(); SVNVersionedProperties props = adminArea.getProperties(fileName); SVNEntry entry = adminArea.getEntry(fileName, true); SVNStatusType mergeResult = adminArea.mergeText(fileName, adminArea.getFile(leftPath), adminArea.getFile(rightPath), null, targetLabel, leftLabel, rightLabel, null, false, null, null); if (props.getPropertyValue(SVNProperty.EXECUTABLE) != null) { SVNFileUtil.setExecutable(target, true); } if (props.getPropertyValue(SVNProperty.NEEDS_LOCK) != null && entry.getLockToken() == null) { SVNFileUtil.setReadonly(target, true); } setEntriesChanged(mergeResult == SVNStatusType.CONFLICTED || mergeResult == SVNStatusType.CONFLICTED_UNRESOLVED); } catch (SVNException svne) { error = svne; if (myIsRerun && (svne.getCause() instanceof FileNotFoundException)) { error = null; } } } else if (SVNLog.COMMIT.equals(name)) { try { SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; if (attributes.getStringValue(SVNLog.REVISION_ATTR) == null) { SVNErrorMessage err = SVNErrorMessage.create(code, "Missing revision attribute for ''{0}''", fileName); SVNErrorManager.error(err, SVNLogType.WC); } SVNEntry entry = adminArea.getEntry(fileName, true); if (myIsRerun && (entry == null || (entry.isScheduledForDeletion() && entry.isDeleted()))) { // skip without an error } else { if (entry == null || (!adminArea.getThisDirName().equals(fileName) && entry.getKind() != SVNNodeKind.FILE)) { SVNErrorMessage err = SVNErrorMessage.create(code, "Log command for directory ''{0}'' is mislocated", adminArea.getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } boolean implicit = attributes.getStringValue("implicit") != null && entry.isCopied(); setEntriesChanged(true); long revisionNumber = -1; try { revisionNumber = Long.parseLong(attributes.getStringValue(SVNLog.REVISION_ATTR)); } catch (NumberFormatException nfe) { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.WC_BAD_ADM_LOG, nfe), SVNLogType.WC); } adminArea.postCommit(fileName, revisionNumber, implicit, myIsRerun, code); } } catch (SVNException svne) { error = svne; } } else { SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; SVNErrorMessage err = SVNErrorMessage.create(code, "Unrecognized logfile element ''{0}'' in ''{1}''", new Object[]{name, adminArea.getRoot()}); SVNErrorManager.error(err.wrap("In directory ''{0}''", adminArea.getRoot()), SVNLogType.WC); } myLogCount = count; if (error != null) { SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; SVNErrorMessage err = SVNErrorMessage.create(code, "Error processing command ''{0}'' in ''{1}''", new Object[]{name, adminArea.getRoot()}); SVNErrorManager.error(err, error, SVNLogType.WC); } } private void setTreeConflictsAdded(boolean added) { myHasAddedTreeConflicts |= added; } private void setEntriesChanged(boolean modified) { myIsEntriesChanged |= modified; } private void setWCPropertiesChanged(boolean modified) { myIsWCPropertiesChanged |= modified; } private void saveTreeConflicts(SVNAdminArea adminArea) throws SVNException { Map attributes = new SVNHashMap(); String conflictData = SVNTreeConflictUtil.getTreeConflictData(getTreeConflicts()); attributes.put(SVNProperty.TREE_CONFLICT_DATA, conflictData); try { adminArea.modifyEntry(adminArea.getThisDirName(), attributes, false, false); } catch (SVNException e) { SVNErrorCode errorCode = myLogCount <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG; SVNErrorMessage error = SVNErrorMessage.create(errorCode, "Error recording tree conflicts in ''{0}''", adminArea.getRoot()); SVNErrorManager.error(error, e, SVNLogType.WC); } myIsEntriesChanged = true; } public void logStarted(SVNAdminArea adminArea) throws SVNException { SVNEntry dirEntry = adminArea.getEntry(adminArea.getThisDirName(), false); Map currentConflicts = dirEntry.getTreeConflicts(); if (currentConflicts != null) { getTreeConflicts().putAll(currentConflicts); } myHasAddedTreeConflicts = false; myLogCount = 0; } public void logFailed(SVNAdminArea adminArea) throws SVNException { if (myHasAddedTreeConflicts) { saveTreeConflicts(adminArea); } if (myIsWCPropertiesChanged) { adminArea.saveWCProperties(true); } else { adminArea.closeWCProperties(); } if (myIsEntriesChanged) { adminArea.saveEntries(false); } else { adminArea.closeEntries(); } myLogCount = 0; } public void logCompleted(SVNAdminArea adminArea) throws SVNException { if (myHasAddedTreeConflicts) { saveTreeConflicts(adminArea); } if (myIsWCPropertiesChanged) { adminArea.saveWCProperties(true); } if (myIsEntriesChanged) { adminArea.saveEntries(false); } adminArea.handleKillMe(); myIsEntriesChanged = false; myIsWCPropertiesChanged = false; myHasAddedTreeConflicts = false; myLogCount = 0; } }