/* * ==================================================================== * 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.io.fs; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNLock; 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.SVNRevisionProperty; import org.tmatesoft.svn.core.internal.util.SVNDate; import org.tmatesoft.svn.core.internal.util.SVNHashSet; 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.io.ISVNLockHandler; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class FSCommitter { private static volatile boolean ourAutoUnlock; private FSFS myFSFS; private FSTransactionRoot myTxnRoot; private FSTransactionInfo myTxn; private Collection<String> myLockTokens; private Map<String, String> myAutoUnlockPaths; private String myAuthor; public static synchronized void setAutoUnlock(boolean autoUnlock) { ourAutoUnlock = autoUnlock; } public static synchronized boolean isAutoUnlock() { return ourAutoUnlock; } public FSCommitter(FSFS fsfs, FSTransactionRoot txnRoot, FSTransactionInfo txn, Collection<String> lockTokens, String author) { myFSFS = fsfs; myTxnRoot = txnRoot; myTxn = txn; myLockTokens = lockTokens != null ? lockTokens : new HashSet<String>(); myAutoUnlockPaths = new HashMap<String, String>(); myAuthor = author; } public Map<String, String> getAutoUnlockPaths() { return myAutoUnlockPaths; } public void deleteNode(String path) throws SVNException { FSTransactionRoot txnRoot = getTxnRoot(); FSParentPath parentPath = txnRoot.openPath(path, true, true); SVNNodeKind kind = parentPath.getRevNode().getType(); if (parentPath.getParent() == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_ROOT_DIR, "The root directory cannot be deleted"); SVNErrorManager.error(err, SVNLogType.FSFS); } if ((txnRoot.getTxnFlags() & FSTransactionRoot.SVN_FS_TXN_CHECK_LOCKS) != 0) { allowLockedOperation(myFSFS, path, myAuthor, myLockTokens, true, false); } makePathMutable(parentPath.getParent(), path); txnRoot.deleteEntry(parentPath.getParent().getRevNode(), parentPath.getEntryName()); txnRoot.removeRevNodeFromCache(parentPath.getAbsPath()); if (myFSFS.supportsMergeInfo()) { long mergeInfoCount = parentPath.getRevNode().getMergeInfoCount(); if (mergeInfoCount > 0) { incrementMergeInfoUpTree(parentPath.getParent(), -mergeInfoCount); } } addChange(path, parentPath.getRevNode().getId(), FSPathChangeKind.FS_PATH_CHANGE_DELETE, false, false, SVNRepository.INVALID_REVISION, null, kind); } public void changeNodeProperty(String path, String name, SVNPropertyValue propValue) throws SVNException { FSRepositoryUtil.validateProperty(name, propValue); FSTransactionRoot txnRoot = getTxnRoot(); FSParentPath parentPath = txnRoot.openPath(path, true, true); SVNNodeKind kind = parentPath.getRevNode().getType(); if ((txnRoot.getTxnFlags() & FSTransactionRoot.SVN_FS_TXN_CHECK_LOCKS) != 0) { allowLockedOperation(myFSFS, path, myAuthor, myLockTokens, false, false); } makePathMutable(parentPath, path); SVNProperties properties = parentPath.getRevNode().getProperties(myFSFS); if (properties.isEmpty() && propValue == null) { return; } if (myFSFS.supportsMergeInfo() && name.equals(SVNProperty.MERGE_INFO)) { long increment = 0; boolean hadMergeInfo = parentPath.getRevNode().hasMergeInfo(); if (propValue != null && !hadMergeInfo) { increment = 1; } else if (propValue == null && hadMergeInfo) { increment = -1; } if (increment != 0) { parentPath.getRevNode().setHasMergeInfo(propValue != null); incrementMergeInfoUpTree(parentPath, increment); } } if (propValue == null) { properties.remove(name); } else { properties.put(name, propValue); } txnRoot.setProplist(parentPath.getRevNode(), properties); addChange(path, parentPath.getRevNode().getId(), FSPathChangeKind.FS_PATH_CHANGE_MODIFY, false, true, SVNRepository.INVALID_REVISION, null, kind); } public void makeCopy(FSRevisionRoot fromRoot, String fromPath, String toPath, boolean preserveHistory) throws SVNException { FSTransactionRoot txnRoot = getTxnRoot(); String txnId = txnRoot.getTxnID(); FSRevisionNode fromNode = fromRoot.getRevisionNode(fromPath); FSParentPath toParentPath = txnRoot.openPath(toPath, false, true); if ((txnRoot.getTxnFlags() & FSTransactionRoot.SVN_FS_TXN_CHECK_LOCKS) != 0) { allowLockedOperation(myFSFS, toPath, myAuthor, myLockTokens, true, false); } if (toParentPath.getRevNode() != null && toParentPath.getRevNode().getId().equals(fromNode.getId())) { return; } FSPathChangeKind changeKind; long mergeInfoStart = 0; if (toParentPath.getRevNode() != null) { changeKind = FSPathChangeKind.FS_PATH_CHANGE_REPLACE; if (myFSFS.supportsMergeInfo()) { mergeInfoStart = toParentPath.getRevNode().getMergeInfoCount(); } } else { changeKind = FSPathChangeKind.FS_PATH_CHANGE_ADD; } makePathMutable(toParentPath.getParent(), toPath); String fromCanonPath = SVNPathUtil.canonicalizeAbsolutePath(fromPath); copy(toParentPath.getParent().getRevNode(), toParentPath.getEntryName(), fromNode, preserveHistory, fromRoot.getRevision(), fromCanonPath, txnId); if (changeKind == FSPathChangeKind.FS_PATH_CHANGE_REPLACE) { txnRoot.removeRevNodeFromCache(toParentPath.getAbsPath()); } long mergeInfoEnd = 0; if (myFSFS.supportsMergeInfo()) { mergeInfoEnd = fromNode.getMergeInfoCount(); if (mergeInfoStart != mergeInfoEnd) { incrementMergeInfoUpTree(toParentPath.getParent(), mergeInfoEnd - mergeInfoStart); } } FSRevisionNode newNode = txnRoot.getRevisionNode(toPath); addChange(toPath, newNode.getId(), changeKind, false, false, fromRoot.getRevision(), fromCanonPath, fromNode.getType()); } public void makeFile(String path) throws SVNException { SVNPathUtil.checkPathIsValid(path); FSTransactionRoot txnRoot = getTxnRoot(); String txnId = txnRoot.getTxnID(); FSParentPath parentPath = txnRoot.openPath(path, false, true); if (parentPath.getRevNode() != null) { SVNErrorManager.error(FSErrors.errorAlreadyExists(txnRoot, path, myFSFS), SVNLogType.FSFS); } if ((txnRoot.getTxnFlags() & FSTransactionRoot.SVN_FS_TXN_CHECK_LOCKS) != 0) { allowLockedOperation(myFSFS, path, myAuthor, myLockTokens, false, false); } makePathMutable(parentPath.getParent(), path); FSRevisionNode childNode = makeEntry(parentPath.getParent().getRevNode(), parentPath.getParent().getAbsPath(), parentPath.getEntryName(), false, txnId); txnRoot.putRevNodeToCache(parentPath.getAbsPath(), childNode); addChange(path, childNode.getId(), FSPathChangeKind.FS_PATH_CHANGE_ADD, false, false, SVNRepository.INVALID_REVISION, null, SVNNodeKind.FILE); } public void makeDir(String path) throws SVNException { SVNPathUtil.checkPathIsValid(path); FSTransactionRoot txnRoot = getTxnRoot(); String txnId = txnRoot.getTxnID(); FSParentPath parentPath = txnRoot.openPath(path, false, true); if (parentPath.getRevNode() != null) { SVNErrorManager.error(FSErrors.errorAlreadyExists(txnRoot, path, myFSFS), SVNLogType.FSFS); } if ((txnRoot.getTxnFlags() & FSTransactionRoot.SVN_FS_TXN_CHECK_LOCKS) != 0) { allowLockedOperation(myFSFS, path, myAuthor, myLockTokens, true, false); } makePathMutable(parentPath.getParent(), path); FSRevisionNode subDirNode = makeEntry(parentPath.getParent().getRevNode(), parentPath.getParent().getAbsPath(), parentPath.getEntryName(), true, txnId); txnRoot.putRevNodeToCache(parentPath.getAbsPath(), subDirNode); addChange(path, subDirNode.getId(), FSPathChangeKind.FS_PATH_CHANGE_ADD, false, false, SVNRepository.INVALID_REVISION, null, SVNNodeKind.DIR); } public FSRevisionNode makeEntry(FSRevisionNode parent, String parentPath, String entryName, boolean isDir, String txnId) throws SVNException { if (!SVNPathUtil.isSinglePathComponent(entryName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_SINGLE_PATH_COMPONENT, "Attempted to create a node with an illegal name ''{0}''", entryName); SVNErrorManager.error(err, SVNLogType.FSFS); } if (parent.getType() != SVNNodeKind.DIR) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_DIRECTORY, "Attempted to create entry in non-directory parent"); SVNErrorManager.error(err, SVNLogType.FSFS); } if (!parent.getId().isTxn()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_MUTABLE, "Attempted to clone child of non-mutable node"); SVNErrorManager.error(err, SVNLogType.FSFS); } FSRevisionNode newRevNode = new FSRevisionNode(); newRevNode.setType(isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE); String createdPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(parentPath, entryName)); newRevNode.setCreatedPath(createdPath); newRevNode.setCopyRootPath(parent.getCopyRootPath()); newRevNode.setCopyRootRevision(parent.getCopyRootRevision()); newRevNode.setCopyFromRevision(SVNRepository.INVALID_REVISION); newRevNode.setCopyFromPath(null); FSID newNodeId = createNode(newRevNode, parent.getId().getCopyID(), txnId); FSRevisionNode childNode = myFSFS.getRevisionNode(newNodeId); FSTransactionRoot txnRoot = getTxnRoot(); txnRoot.setEntry(parent, entryName, childNode.getId(), newRevNode.getType()); return childNode; } public void addChange(String path, FSID id, FSPathChangeKind changeKind, boolean textModified, boolean propsModified, long copyFromRevision, String copyFromPath, SVNNodeKind kind) throws SVNException { path = SVNPathUtil.canonicalizeAbsolutePath(path); OutputStream changesFile = null; try { FSTransactionRoot txnRoot = getTxnRoot(); changesFile = SVNFileUtil.openFileForWriting(txnRoot.getTransactionChangesFile(), true); FSPathChange pathChange = new FSPathChange(path, id, changeKind, textModified, propsModified, copyFromPath, copyFromRevision, kind); txnRoot.writeChangeEntry(changesFile, pathChange, true); } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } finally { SVNFileUtil.closeFile(changesFile); } } public long commitTxn(boolean runPreCommitHook, boolean runPostCommitHook, SVNErrorMessage[] postCommitHookError, StringBuffer conflictPath) throws SVNException { if (myFSFS.isHooksEnabled() && runPreCommitHook) { FSHooks.runPreCommitHook(myFSFS.getRepositoryRoot(), myTxn.getTxnId()); } final SVNProperties txnProperties = myFSFS.getTransactionProperties(myTxn.getTxnId()); for (String propertyName : new HashSet<String>(txnProperties.nameSet())) { if (propertyName.startsWith(SVNRevisionProperty.SVN_TXN_PREFIX)) { myFSFS.setTransactionProperty(myTxn.getTxnId(), propertyName, null); } } long newRevision = SVNRepository.INVALID_REVISION; while (true) { long youngishRev = myFSFS.getYoungestRevision(); FSRevisionRoot youngishRoot = myFSFS.createRevisionRoot(youngishRev); FSRevisionNode youngishRootNode = youngishRoot.getRevisionNode("/"); mergeChanges(myFSFS, getTxnRoot(), youngishRootNode, conflictPath); myTxn.setBaseRevision(youngishRev); FSWriteLock writeLock = FSWriteLock.getWriteLockForDB(myFSFS); final Collection<FSRepresentation> representations = myFSFS.getRepositoryCacheManager() != null ? new ArrayList<FSRepresentation>() : null; synchronized (writeLock) { try { writeLock.lock(); newRevision = commit(representations); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_TXN_OUT_OF_DATE) { long youngestRev = myFSFS.getYoungestRevision(); if (youngishRev == youngestRev) { throw svne; } continue; } throw svne; } finally { writeLock.unlock(); FSWriteLock.release(writeLock); } } // write representations here. if (representations != null && !representations.isEmpty()) { if (myFSFS.getRepositoryCacheManager() != null) { try { myFSFS.getRepositoryCacheManager().runWriteTransaction(new IFSSqlJetTransaction() { public void run() throws SVNException { for (FSRepresentation fsRepresentation : representations) { myFSFS.getRepositoryCacheManager().insert(fsRepresentation, false); } } }); } catch (SVNException e) { // ignore SVNDebugLog.getDefaultLog().logError(SVNLogType.FSFS, e); } } } break; } if (myFSFS.isHooksEnabled() && runPostCommitHook) { try { FSHooks.runPostCommitHook(myFSFS.getRepositoryRoot(), newRevision); } catch (SVNException svne) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.REPOS_POST_COMMIT_HOOK_FAILED, "Commit succeeded, but post-commit hook failed", SVNErrorMessage.TYPE_WARNING); SVNErrorMessage childErr = svne.getErrorMessage(); childErr.setDontShowErrorCode(true); errorMessage.setChildErrorMessage(childErr); if (postCommitHookError != null && postCommitHookError.length > 0) { postCommitHookError[0] = errorMessage; } else { SVNErrorManager.error(errorMessage, SVNLogType.FSFS); } } } return newRevision; } public void makePathMutable(FSParentPath parentPath, String errorPath) throws SVNException { FSTransactionRoot txnRoot = getTxnRoot(); String txnId = txnRoot.getTxnID(); if (parentPath.getRevNode().getId().isTxn()) { return; } FSRevisionNode clone = null; if (parentPath.getParent() != null) { makePathMutable(parentPath.getParent(), errorPath); FSID parentId = null; String copyId = null; switch (parentPath.getCopyStyle()) { case FSCopyInheritance.COPY_ID_INHERIT_PARENT: parentId = parentPath.getParent().getRevNode().getId(); copyId = parentId.getCopyID(); break; case FSCopyInheritance.COPY_ID_INHERIT_NEW: copyId = reserveCopyId(txnId); break; case FSCopyInheritance.COPY_ID_INHERIT_SELF: copyId = null; break; case FSCopyInheritance.COPY_ID_INHERIT_UNKNOWN: default: SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "FATAL error: can not make path ''{0}'' mutable", errorPath); SVNErrorManager.error(err, SVNLogType.FSFS); } String copyRootPath = parentPath.getRevNode().getCopyRootPath(); long copyRootRevision = parentPath.getRevNode().getCopyRootRevision(); FSRoot copyrootRoot = myFSFS.createRevisionRoot(copyRootRevision); FSRevisionNode copyRootNode = copyrootRoot.getRevisionNode(copyRootPath); FSID childId = parentPath.getRevNode().getId(); FSID copyRootId = copyRootNode.getId(); boolean isParentCopyRoot = false; if (!childId.getNodeID().equals(copyRootId.getNodeID())) { isParentCopyRoot = true; } String clonePath = parentPath.getParent().getAbsPath(); clone = txnRoot.cloneChild(parentPath.getParent().getRevNode(), clonePath, parentPath.getEntryName(), copyId, isParentCopyRoot); txnRoot.putRevNodeToCache(parentPath.getAbsPath(), clone); } else { FSTransactionInfo txn = txnRoot.getTxn(); if (txn.getRootID().equals(txn.getBaseID())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "FATAL error: txn ''{0}'' root id ''{1}'' matches base id ''{2}''", new Object[] { txnId, txn.getRootID(), txn.getBaseID() }); SVNErrorManager.error(err, SVNLogType.FSFS); } clone = myFSFS.getRevisionNode(txn.getRootID()); } parentPath.setRevNode(clone); } public String reserveCopyId(String txnId) throws SVNException { FSTransactionRoot txnRoot = getTxnRoot(); String[] nextIds = txnRoot.readNextIDs(); String copyId = FSRepositoryUtil.generateNextKey(nextIds[1]); myFSFS.writeNextIDs(txnId, nextIds[0], copyId); return "_" + nextIds[1]; } public void incrementMergeInfoUpTree(FSParentPath parentPath, long increment) throws SVNException { while (parentPath != null) { FSTransactionRoot txnRoot = getTxnRoot(); txnRoot.incrementMergeInfoCount(parentPath.getRevNode(), increment); parentPath = parentPath.getParent(); } } private void copy(FSRevisionNode toNode, String entryName, FSRevisionNode fromNode, boolean preserveHistory, long fromRevision, String fromPath, String txnId) throws SVNException { FSID id = null; FSTransactionRoot txnRoot = getTxnRoot(); if (preserveHistory) { FSID srcId = fromNode.getId(); FSRevisionNode toRevNode = FSRevisionNode.dumpRevisionNode(fromNode); String copyId = reserveCopyId(txnId); toRevNode.setPredecessorId(srcId); if (toRevNode.getCount() != -1) { toRevNode.setCount(toRevNode.getCount() + 1); } String createdPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(toNode.getCreatedPath(), entryName)); toRevNode.setCreatedPath(createdPath); toRevNode.setCopyFromPath(fromPath); toRevNode.setCopyFromRevision(fromRevision); toRevNode.setCopyRootPath(null); id = txnRoot.createSuccessor(srcId, toRevNode, copyId); } else { id = fromNode.getId(); } txnRoot.setEntry(toNode, entryName, id, fromNode.getType()); } private FSID createNode(FSRevisionNode revNode, String copyId, String txnId) throws SVNException { FSTransactionRoot txnRoot = getTxnRoot(); String nodeId = txnRoot.getNewTxnNodeId(); FSID id = FSID.createTxnId(nodeId, copyId, txnId); revNode.setId(id); revNode.setIsFreshTxnRoot(false); myFSFS.putTxnRevisionNode(id, revNode); return id; } private long commit(Collection<FSRepresentation> representations) throws SVNException { long oldRev = myFSFS.getYoungestRevision(); if (myTxn.getBaseRevision() != oldRev) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_TXN_OUT_OF_DATE, "Transaction out of date"); SVNErrorManager.error(err, SVNLogType.FSFS); } verifyLocks(); final String startNodeId; final String startCopyId; if (myFSFS.getDBFormat() < FSFS.MIN_NO_GLOBAL_IDS_FORMAT) { String[] ids = myFSFS.getNextRevisionIDs(); startNodeId = ids[0]; startCopyId = ids[1]; } else { startNodeId = null; startCopyId = null; } final long newRevision = oldRev + 1; final OutputStream protoFileOS = null; final FSID newRootId = null; final FSTransactionRoot txnRoot = getTxnRoot(); FSWriteLock txnWriteLock = FSWriteLock.getWriteLockForTxn(myTxn.getTxnId(), myFSFS); synchronized (txnWriteLock) { try { // start transaction. txnWriteLock.lock(); final File revisionPrototypeFile = txnRoot.getTransactionProtoRevFile(); final long offset = revisionPrototypeFile.length(); commit(startNodeId, startCopyId, newRevision, protoFileOS, newRootId, txnRoot, revisionPrototypeFile, offset, representations); File dstRevFile = myFSFS.getNewRevisionFile(newRevision); SVNFileUtil.rename(revisionPrototypeFile, dstRevFile); } finally { txnWriteLock.unlock(); FSWriteLock.release(txnWriteLock); } } String commitTime = SVNDate.formatDate(new Date(System.currentTimeMillis())); SVNProperties presetRevisionProperties = myFSFS.getTransactionProperties(myTxn.getTxnId()); if (presetRevisionProperties == null || !presetRevisionProperties.containsName(SVNRevisionProperty.DATE)) { myFSFS.setTransactionProperty(myTxn.getTxnId(), SVNRevisionProperty.DATE, SVNPropertyValue.create(commitTime)); } File txnPropsFile = myFSFS.getTransactionPropertiesFile(myTxn.getTxnId()); if (myFSFS.getDBFormat() < FSFS.MIN_PACKED_REVPROP_FORMAT || newRevision >= myFSFS.getMinUnpackedRevProp()){ File dstRevPropsFile = myFSFS.getNewRevisionPropertiesFile(newRevision); SVNFileUtil.rename(txnPropsFile, dstRevPropsFile); } else { // TODO pack property? } try { txnRoot.writeFinalCurrentFile(newRevision, startNodeId, startCopyId); } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } myFSFS.setYoungestRevisionCache(newRevision); myFSFS.purgeTxn(myTxn.getTxnId()); return newRevision; } private void commit(String startNodeId, String startCopyId, long newRevision, OutputStream protoFileOS, FSID newRootId, FSTransactionRoot txnRoot, File revisionPrototypeFile, long offset, Collection<FSRepresentation> representations) throws SVNException { try { protoFileOS = SVNFileUtil.openFileForWriting(revisionPrototypeFile, true); FSID rootId = FSID.createTxnId("0", "0", myTxn.getTxnId()); CountingOutputStream revWriter = new CountingOutputStream(protoFileOS, offset); newRootId = txnRoot.writeFinalRevision(newRootId, revWriter, newRevision, rootId, startNodeId, startCopyId, representations); long changedPathOffset = txnRoot.writeFinalChangedPathInfo(revWriter); String offsetsLine = "\n" + newRootId.getOffset() + " " + changedPathOffset + "\n"; protoFileOS.write(offsetsLine.getBytes("UTF-8")); } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } finally { SVNFileUtil.closeFile(protoFileOS); } SVNProperties txnProps = myFSFS.getTransactionProperties(myTxn.getTxnId()); if (txnProps != null && !txnProps.isEmpty()) { if (txnProps.getStringValue(SVNProperty.TXN_CHECK_OUT_OF_DATENESS) != null) { myFSFS.setTransactionProperty(myTxn.getTxnId(), SVNProperty.TXN_CHECK_OUT_OF_DATENESS, null); } if (txnProps.getStringValue(SVNProperty.TXN_CHECK_LOCKS) != null) { myFSFS.setTransactionProperty(myTxn.getTxnId(), SVNProperty.TXN_CHECK_LOCKS, null); } } } public static void mergeChanges(FSFS owner, FSTransactionRoot txnRoot, FSRevisionNode sourceNode, StringBuffer conflictPath) throws SVNException { FSRevisionNode txnRootNode = txnRoot.getRootRevisionNode(); FSRevisionNode ancestorNode = txnRoot.getTxnBaseRootNode(); if (txnRootNode.getId().equals(ancestorNode.getId())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "FATAL error: no changes in transaction to commit"); SVNErrorManager.error(err, SVNLogType.FSFS); } else { merge(owner, "/", txnRootNode, sourceNode, ancestorNode, txnRoot, conflictPath); } } private static long merge(FSFS owner, String targetPath, FSRevisionNode target, FSRevisionNode source, FSRevisionNode ancestor, FSTransactionRoot txnRoot, StringBuffer conflictPath) throws SVNException { FSID sourceId = source.getId(); FSID targetId = target.getId(); FSID ancestorId = ancestor.getId(); long mergeInfoIncrement = 0; if (ancestorId.equals(targetId)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_GENERAL, "Bad merge; target ''{0}'' has id ''{1}'', same as ancestor", new Object[] { targetPath, targetId }); SVNErrorManager.error(err, SVNLogType.FSFS); } if (ancestorId.equals(sourceId) || sourceId.equals(targetId)) { return mergeInfoIncrement; } if (source.getType() != SVNNodeKind.DIR || target.getType() != SVNNodeKind.DIR || ancestor.getType() != SVNNodeKind.DIR) { SVNErrorManager.error(FSErrors.errorConflict(targetPath, conflictPath), SVNLogType.FSFS); } if (!FSRepresentation.compareRepresentations(target.getPropsRepresentation(), ancestor.getPropsRepresentation())) { SVNErrorManager.error(FSErrors.errorConflict(targetPath, conflictPath), SVNLogType.FSFS); } if (!FSRepresentation.compareRepresentations(source.getPropsRepresentation(), ancestor.getPropsRepresentation())) { SVNErrorManager.error(FSErrors.errorConflict(targetPath, conflictPath), SVNLogType.FSFS); } Map sourceEntries = source.getDirEntries(owner); Map targetEntries = target.getDirEntries(owner); Map ancestorEntries = ancestor.getDirEntries(owner); Set removedEntries = new SVNHashSet(); for (Iterator ancestorEntryNames = ancestorEntries.keySet().iterator(); ancestorEntryNames.hasNext();) { String ancestorEntryName = (String) ancestorEntryNames.next(); FSEntry ancestorEntry = (FSEntry) ancestorEntries.get(ancestorEntryName); FSEntry sourceEntry = removedEntries.contains(ancestorEntryName) ? null : (FSEntry) sourceEntries.get(ancestorEntryName); FSEntry targetEntry = (FSEntry) targetEntries.get(ancestorEntryName); if (sourceEntry != null && ancestorEntry.getId().equals(sourceEntry.getId())) { /* * No changes were made to this entry while the transaction was * in progress, so do nothing to the target. */ } else if (targetEntry != null && ancestorEntry.getId().equals(targetEntry.getId())) { if (owner.supportsMergeInfo()) { FSRevisionNode targetEntryNode = owner.getRevisionNode(targetEntry.getId()); long mergeInfoStart = targetEntryNode.getMergeInfoCount(); mergeInfoIncrement -= mergeInfoStart; } if (sourceEntry != null) { if (owner.supportsMergeInfo()) { FSRevisionNode sourceEntryNode = owner.getRevisionNode(sourceEntry.getId()); long mergeInfoEnd = sourceEntryNode.getMergeInfoCount(); mergeInfoIncrement += mergeInfoEnd; } txnRoot.setEntry(target, ancestorEntryName, sourceEntry.getId(), sourceEntry.getType()); } else { txnRoot.deleteEntry(target, ancestorEntryName); } } else { if (sourceEntry == null || targetEntry == null) { SVNErrorManager.error(FSErrors.errorConflict(SVNPathUtil.getAbsolutePath(SVNPathUtil.append(targetPath, ancestorEntryName)), conflictPath), SVNLogType.FSFS); } if (sourceEntry.getType() == SVNNodeKind.FILE || targetEntry.getType() == SVNNodeKind.FILE || ancestorEntry.getType() == SVNNodeKind.FILE) { SVNErrorManager.error(FSErrors.errorConflict(SVNPathUtil.getAbsolutePath(SVNPathUtil.append(targetPath, ancestorEntryName)), conflictPath), SVNLogType.FSFS); } if (!sourceEntry.getId().getNodeID().equals(ancestorEntry.getId().getNodeID()) || !sourceEntry.getId().getCopyID().equals(ancestorEntry.getId().getCopyID()) || !targetEntry.getId().getNodeID().equals(ancestorEntry.getId().getNodeID()) || !targetEntry.getId().getCopyID().equals(ancestorEntry.getId().getCopyID())) { SVNErrorManager.error(FSErrors.errorConflict(SVNPathUtil.getAbsolutePath(SVNPathUtil.append(targetPath, ancestorEntryName)), conflictPath), SVNLogType.FSFS); } FSRevisionNode sourceEntryNode = owner.getRevisionNode(sourceEntry.getId()); FSRevisionNode targetEntryNode = owner.getRevisionNode(targetEntry.getId()); FSRevisionNode ancestorEntryNode = owner.getRevisionNode(ancestorEntry.getId()); String childTargetPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(targetPath, targetEntry.getName())); long subMergeInfoIncrement = merge(owner, childTargetPath, targetEntryNode, sourceEntryNode, ancestorEntryNode, txnRoot, conflictPath); if (owner.supportsMergeInfo()) { mergeInfoIncrement += subMergeInfoIncrement; } } removedEntries.add(ancestorEntryName); } for (Iterator sourceEntryNames = sourceEntries.keySet().iterator(); sourceEntryNames.hasNext();) { String sourceEntryName = (String) sourceEntryNames.next(); if (removedEntries.contains(sourceEntryName)){ continue; } FSEntry sourceEntry = (FSEntry) sourceEntries.get(sourceEntryName); FSEntry targetEntry = (FSEntry) targetEntries.get(sourceEntryName); if (targetEntry != null) { SVNErrorManager.error(FSErrors.errorConflict(SVNPathUtil.getAbsolutePath(SVNPathUtil.append(targetPath, targetEntry.getName())), conflictPath), SVNLogType.FSFS); } if (owner.supportsMergeInfo()) { FSRevisionNode sourceEntryNode = owner.getRevisionNode(sourceEntry.getId()); long mergeInfoCount = sourceEntryNode.getMergeInfoCount(); mergeInfoIncrement += mergeInfoCount; } txnRoot.setEntry(target, sourceEntry.getName(), sourceEntry.getId(), sourceEntry.getType()); } updateAncestry(owner, sourceId, targetId); if (owner.supportsMergeInfo()) { txnRoot.incrementMergeInfoCount(target, mergeInfoIncrement); } return mergeInfoIncrement; } private static void updateAncestry(FSFS owner, FSID sourceId, FSID targetId) throws SVNException { if (!targetId.isTxn()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_MUTABLE, "Attempt to update ancestry of non-mutable node"); SVNErrorManager.error(err, SVNLogType.FSFS); } final FSRevisionNode targetNode = owner.getRevisionNode(targetId); final FSRevisionNode sourceNode = owner.getRevisionNode(sourceId); targetNode.setPredecessorId(sourceNode.getId()); final long sourcePredecessorCount = sourceNode.getCount(); targetNode.setPredecessorId(sourceId); targetNode.setCount(sourcePredecessorCount != -1 ? sourcePredecessorCount + 1 : sourcePredecessorCount); targetNode.setIsFreshTxnRoot(false); owner.putTxnRevisionNode(targetId, targetNode); } private void verifyLocks() throws SVNException { FSTransactionRoot txnRoot = getTxnRoot(); Map changes = txnRoot.getChangedPaths(); Object[] changedPaths = changes.keySet().toArray(); Arrays.sort(changedPaths); String lastRecursedPath = null; for (int i = 0; i < changedPaths.length; i++) { String changedPath = (String) changedPaths[i]; boolean recurse = true; if (lastRecursedPath != null && SVNPathUtil.getPathAsChild(lastRecursedPath, changedPath) != null) { continue; } FSPathChange change = (FSPathChange) changes.get(changedPath); if (change.getChangeKind() == FSPathChangeKind.FS_PATH_CHANGE_MODIFY) { recurse = false; } allowLockedOperation(myFSFS, changedPath, myAuthor, myLockTokens, recurse, true); if (recurse) { lastRecursedPath = changedPath; } } } private FSTransactionRoot getTxnRoot() throws SVNException { if (myTxnRoot == null && myTxn != null) { myTxnRoot = myFSFS.createTransactionRoot(myTxn); } return myTxnRoot; } public void allowLockedOperation(FSFS fsfs, String path, final String username, final Collection<String> lockTokens, boolean recursive, boolean haveWriteLock) throws SVNException { path = SVNPathUtil.canonicalizeAbsolutePath(path); if (recursive) { ISVNLockHandler handler = new ISVNLockHandler() { public void handleLock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { if (isAutoUnlock()) { scheduleForAutoUnlock(username, path, lock); } else { verifyLock(lock, lockTokens, username); } } public void handleUnlock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { } }; fsfs.walkDigestFiles(fsfs.getDigestFileFromRepositoryPath(path), handler, haveWriteLock); } else { SVNLock lock = fsfs.getLockHelper(path, haveWriteLock); if (lock != null) { if (isAutoUnlock()) { scheduleForAutoUnlock(username, path, lock); } else { verifyLock(lock, lockTokens, username); } } } } private void scheduleForAutoUnlock(final String username, String path, SVNLock lock) { myAutoUnlockPaths.put(path, lock.getID()); } private void verifyLock(SVNLock lock, Collection<String> lockTokens, String username) throws SVNException { if (username == null || "".equals(username)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_USER, "Cannot verify lock on path ''{0}''; no username available", lock.getPath()); SVNErrorManager.error(err, SVNLogType.FSFS); } else if (username.compareTo(lock.getOwner()) != 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_LOCK_OWNER_MISMATCH, "User {0} does not own lock on path ''{1}'' (currently locked by {2})", new Object[] { username, lock.getPath(), lock.getOwner() }); SVNErrorManager.error(err, SVNLogType.FSFS); } if (lockTokens.contains(lock.getID())) { return; } SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_BAD_LOCK_TOKEN, "Cannot verify lock on path ''{0}''; no matching lock-token available", lock.getPath()); SVNErrorManager.error(err, SVNLogType.FSFS); } public static void abortTransaction(FSFS fsfs, String txnId) throws SVNException { File txnDir = fsfs.getTransactionDir(txnId); SVNFileUtil.deleteAll(txnDir, true); if (txnDir.exists()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Transaction cleanup failed"); SVNErrorManager.error(err, SVNLogType.FSFS); } } }