/*
* ====================================================================
* 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.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
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.SVNRevisionProperty;
import org.tmatesoft.svn.core.internal.util.SVNDate;
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.SVNWCProperties;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class FSTransactionRoot extends FSRoot {
public static final int SVN_FS_TXN_CHECK_OUT_OF_DATENESS = 0x00001;
public static final int SVN_FS_TXN_CHECK_LOCKS = 0x00002;
private String myTxnID;
private int myTxnFlags;
private File myTxnChangesFile;
private File myTxnRevFile;
private long myBaseRevision;
public FSTransactionRoot(FSFS owner, String txnID, long baseRevision, int flags) {
super(owner);
myTxnID = txnID;
myTxnFlags = flags;
myBaseRevision = baseRevision;
}
public long getRevision() {
return myBaseRevision;
}
public FSCopyInheritance getCopyInheritance(FSParentPath child) throws SVNException {
if (child == null || child.getParent() == null || myTxnID == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "FATAL error: invalid txn name or child");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSID childID = child.getRevNode().getId();
FSID parentID = child.getParent().getRevNode().getId();
String childCopyID = childID.getCopyID();
String parentCopyID = parentID.getCopyID();
if (childID.isTxn()) {
return new FSCopyInheritance(FSCopyInheritance.COPY_ID_INHERIT_SELF, null);
}
FSCopyInheritance copyInheritance = new FSCopyInheritance(FSCopyInheritance.COPY_ID_INHERIT_PARENT, null);
if (childCopyID.compareTo("0") == 0) {
return copyInheritance;
}
if (childCopyID.compareTo(parentCopyID) == 0) {
return copyInheritance;
}
long copyrootRevision = child.getRevNode().getCopyRootRevision();
String copyrootPath = child.getRevNode().getCopyRootPath();
FSRoot copyrootRoot = getOwner().createRevisionRoot(copyrootRevision);
FSRevisionNode copyrootNode = copyrootRoot.getRevisionNode(copyrootPath);
FSID copyrootID = copyrootNode.getId();
if (copyrootID.compareTo(childID) == -1) {
return copyInheritance;
}
String idPath = child.getRevNode().getCreatedPath();
if (idPath.compareTo(child.getAbsPath()) == 0) {
copyInheritance.setStyle(FSCopyInheritance.COPY_ID_INHERIT_SELF);
return copyInheritance;
}
copyInheritance.setStyle(FSCopyInheritance.COPY_ID_INHERIT_NEW);
copyInheritance.setCopySourcePath(idPath);
return copyInheritance;
}
public FSRevisionNode getRootRevisionNode() throws SVNException {
if (myRootRevisionNode == null) {
FSTransactionInfo txn = getTxn();
myRootRevisionNode = getOwner().getRevisionNode(txn.getRootID());
}
return myRootRevisionNode;
}
public FSRevisionNode getTxnBaseRootNode() throws SVNException {
FSTransactionInfo txn = getTxn();
FSRevisionNode baseRootNode = getOwner().getRevisionNode(txn.getBaseID());
return baseRootNode;
}
public FSTransactionInfo getTxn() throws SVNException {
FSID rootID = FSID.createTxnId("0", "0", myTxnID);
FSRevisionNode revNode = getOwner().getRevisionNode(rootID);
FSTransactionInfo txn = new FSTransactionInfo(revNode.getId(), revNode.getPredecessorId());
return txn;
}
public Map getChangedPaths() throws SVNException {
FSFile file = getOwner().getTransactionChangesFile(myTxnID);
try {
return fetchAllChanges(file, false);
} finally {
file.close();
}
}
public int getTxnFlags() {
return myTxnFlags;
}
public void setTxnFlags(int txnFlags) {
myTxnFlags = txnFlags;
}
public String getTxnID() {
return myTxnID;
}
public SVNProperties unparseDirEntries(Map entries) {
SVNProperties unparsedEntries = new SVNProperties();
for (Iterator names = entries.keySet().iterator(); names.hasNext();) {
String name = (String) names.next();
FSEntry dirEntry = (FSEntry) entries.get(name);
String unparsedVal = dirEntry.toString();
unparsedEntries.put(name, unparsedVal);
}
return unparsedEntries;
}
public static FSTransactionInfo beginTransactionForCommit(long baseRevision, SVNProperties revisionProperties, FSFS owner) throws SVNException {
List caps = new ArrayList();
caps.add("mergeinfo");
String author = revisionProperties.getStringValue(SVNRevisionProperty.AUTHOR);
if (owner != null && owner.isHooksEnabled()) {
FSHooks.runStartCommitHook(owner.getRepositoryRoot(), author, caps);
}
FSTransactionInfo txn = FSTransactionRoot.beginTransaction(baseRevision, FSTransactionRoot.SVN_FS_TXN_CHECK_LOCKS, owner);
owner.changeTransactionProperties(txn.getTxnId(), revisionProperties);
return txn;
}
public static FSTransactionInfo beginTransaction(long baseRevision, int flags, FSFS owner) throws SVNException {
FSTransactionInfo txn = createTxn(baseRevision, owner);
String commitTime = SVNDate.formatDate(new Date(System.currentTimeMillis()));
owner.setTransactionProperty(txn.getTxnId(), SVNRevisionProperty.DATE, SVNPropertyValue.create(commitTime));
if ((flags & SVN_FS_TXN_CHECK_OUT_OF_DATENESS) != 0) {
owner.setTransactionProperty(txn.getTxnId(), SVNProperty.TXN_CHECK_OUT_OF_DATENESS, SVNPropertyValue.create(Boolean.TRUE.toString()));
}
if ((flags & FSTransactionRoot.SVN_FS_TXN_CHECK_LOCKS) != 0) {
owner.setTransactionProperty(txn.getTxnId(), SVNProperty.TXN_CHECK_LOCKS, SVNPropertyValue.create(Boolean.TRUE.toString()));
}
return txn;
}
private static FSTransactionInfo createTxn(long baseRevision, FSFS owner) throws SVNException {
String txnId = null;
if (owner.getDBFormat() >= FSFS.MIN_CURRENT_TXN_FORMAT) {
txnId = createTxnDir(baseRevision, owner);
} else {
txnId = createPre15TxnDir(baseRevision, owner);
}
FSTransactionInfo txn = new FSTransactionInfo(baseRevision, txnId);
FSRevisionRoot root = owner.createRevisionRoot(baseRevision);
FSRevisionNode rootNode = root.getRootRevisionNode();
owner.createNewTxnNodeRevisionFromRevision(txnId, rootNode);
SVNFileUtil.createEmptyFile(owner.getTransactionProtoRevFile(txn.getTxnId()));
SVNFileUtil.createEmptyFile(owner.getTransactionProtoRevLockFile(txn.getTxnId()));
SVNFileUtil.createEmptyFile(new File(owner.getTransactionDir(txn.getTxnId()), "changes"));
owner.writeNextIDs(txnId, "0", "0");
return txn;
}
private static String createTxnDir(long revision, FSFS owner) throws SVNException {
String txnId = owner.getAndIncrementTxnKey();
txnId = String.valueOf(revision) + "-" + txnId;
File parent = owner.getTransactionsParentDir();
File txnDir = new File(parent, txnId + FSFS.TXN_PATH_EXT);
txnDir.mkdirs();
return txnId;
}
private static String createPre15TxnDir(long revision, FSFS owner) throws SVNException {
File parent = owner.getTransactionsParentDir();
File uniquePath = null;
for (int i = 1; i < 99999; i++) {
String txnId = String.valueOf(revision) + "-" + String.valueOf(i);
uniquePath = new File(parent, txnId + FSFS.TXN_PATH_EXT);
if (!uniquePath.exists() && uniquePath.mkdirs()) {
return txnId;
}
if (!uniquePath.exists()) {
break;
}
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_UNIQUE_NAMES_EXHAUSTED,
"Unable to create transaction directory in ''{0}'' for revision {1}",
new Object[] { parent, new Long(revision) });
SVNErrorManager.error(err, SVNLogType.FSFS);
return null;
}
public void deleteEntry(FSRevisionNode parent, String entryName) throws SVNException {
if (parent.getType() != SVNNodeKind.DIR) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_DIRECTORY, "Attempted to delete entry ''{0}'' from *non*-directory node", entryName);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (!parent.getId().isTxn()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_MUTABLE, "Attempted to delete entry ''{0}'' from immutable directory node", entryName);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (!SVNPathUtil.isSinglePathComponent(entryName)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_SINGLE_PATH_COMPONENT, "Attempted to delete a node with an illegal name ''{0}''", entryName);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
Map entries = parent.getDirEntries(getOwner());
FSEntry dirEntry = (FSEntry) entries.get(entryName);
if (dirEntry == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_ENTRY, "Delete failed--directory has no entry ''{0}''", entryName);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
getOwner().getRevisionNode(dirEntry.getId());
deleteEntryIfMutable(dirEntry.getId());
setEntry(parent, entryName, null, SVNNodeKind.UNKNOWN);
}
public void incrementMergeInfoCount(FSRevisionNode node, long increment) throws SVNException {
if (!node.getId().isTxn()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_MUTABLE,
"Can''t increment mergeinfo count on *immutable* node-revision {0}", node.getId());
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (increment == 0) {
return;
}
node.setMergeInfoCount(node.getMergeInfoCount() + increment);
if (node.getMergeInfoCount() < 0) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT,
"Can''t increment mergeinfo count on node-revision {0} to negative value {1}",
new Object[] { node.getId(), new Long(node.getMergeInfoCount()) });
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (node.getMergeInfoCount() > 1 && node.getType() == SVNNodeKind.FILE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT,
"Can''t increment mergeinfo count on *file* node-revision {0} to {1} (> 1)",
new Object[] { node.getId(), new Long(node.getMergeInfoCount()) });
SVNErrorManager.error(err, SVNLogType.FSFS);
}
getOwner().putTxnRevisionNode(node.getId(), node);
}
private void deleteEntryIfMutable(FSID id) throws SVNException {
FSRevisionNode node = getOwner().getRevisionNode(id);
if (!node.getId().isTxn()) {
return;
}
if (node.getType() == SVNNodeKind.DIR) {
Map entries = node.getDirEntries(getOwner());
for (Iterator names = entries.keySet().iterator(); names.hasNext();) {
String name = (String) names.next();
FSEntry entry = (FSEntry) entries.get(name);
deleteEntryIfMutable(entry.getId());
}
}
removeRevisionNode(id);
}
private void removeRevisionNode(FSID id) throws SVNException {
FSRevisionNode node = getOwner().getRevisionNode(id);
if (!node.getId().isTxn()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_MUTABLE, "Attempted removal of immutable node");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (node.getPropsRepresentation() != null && node.getPropsRepresentation().isTxn()) {
SVNFileUtil.deleteFile(getTransactionRevNodePropsFile(id));
}
if (node.getTextRepresentation() != null && node.getTextRepresentation().isTxn() && node.getType() == SVNNodeKind.DIR) {
SVNFileUtil.deleteFile(getTransactionRevNodeChildrenFile(id));
}
SVNFileUtil.deleteFile(getOwner().getTransactionRevNodeFile(id));
}
public void setProplist(FSRevisionNode node, SVNProperties properties) throws SVNException {
if (!node.getId().isTxn()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_MUTABLE, "Can't set proplist on *immutable* node-revision {0}", node.getId());
SVNErrorManager.error(err, SVNLogType.FSFS);
}
File propsFile = getTransactionRevNodePropsFile(node.getId());
SVNWCProperties.setProperties(properties, propsFile,
SVNFileUtil.createUniqueFile(propsFile.getParentFile(),
".props", ".tmp", false),
SVNWCProperties.SVN_HASH_TERMINATOR);
if (node.getPropsRepresentation() == null || !node.getPropsRepresentation().isTxn()) {
FSRepresentation mutableRep = new FSRepresentation();
mutableRep.setTxnId(node.getId().getTxnID());
node.setPropsRepresentation(mutableRep);
node.setIsFreshTxnRoot(false);
getOwner().putTxnRevisionNode(node.getId(), node);
}
}
public FSID createSuccessor(FSID oldId, FSRevisionNode newRevNode, String copyId) throws SVNException {
if (copyId == null) {
copyId = oldId.getCopyID();
}
FSID id = FSID.createTxnId(oldId.getNodeID(), copyId, myTxnID);
newRevNode.setId(id);
if (newRevNode.getCopyRootPath() == null) {
newRevNode.setCopyRootPath(newRevNode.getCreatedPath());
newRevNode.setCopyRootRevision(newRevNode.getId().getRevision());
}
newRevNode.setIsFreshTxnRoot(false);
getOwner().putTxnRevisionNode(newRevNode.getId(), newRevNode);
return id;
}
public void setEntry(FSRevisionNode parentRevNode, String entryName, FSID entryId, SVNNodeKind kind) throws SVNException {
if (parentRevNode.getType() != SVNNodeKind.DIR) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_DIRECTORY, "Attempted to set entry in non-directory node");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (!parentRevNode.getId().isTxn()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_MUTABLE, "Attempted to set entry in immutable node");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSRepresentation textRep = parentRevNode.getTextRepresentation();
File childrenFile = getTransactionRevNodeChildrenFile(parentRevNode.getId());
OutputStream dst = null;
try {
if (textRep == null || !textRep.isTxn()) {
Map entries = parentRevNode.getDirEntries(getOwner());
SVNProperties unparsedEntries = unparseDirEntries(entries);
dst = SVNFileUtil.openFileForWriting(childrenFile);
SVNWCProperties.setProperties(unparsedEntries, dst, SVNWCProperties.SVN_HASH_TERMINATOR);
textRep = new FSRepresentation();
textRep.setRevision(SVNRepository.INVALID_REVISION);
textRep.setTxnId(myTxnID);
String uniqueSuffix = getNewTxnNodeId();
String uniquifier = myTxnID + '/' + uniqueSuffix;
textRep.setUniquifier(uniquifier);
parentRevNode.setTextRepresentation(textRep);
parentRevNode.setIsFreshTxnRoot(false);
getOwner().putTxnRevisionNode(parentRevNode.getId(), parentRevNode);
} else {
dst = SVNFileUtil.openFileForWriting(childrenFile, true);
}
Map dirContents = parentRevNode.getDirContents();
if (entryId != null) {
SVNWCProperties.appendProperty(entryName, SVNPropertyValue.create(kind + " " + entryId.toString()), dst);
if (dirContents != null) {
dirContents.put(entryName, new FSEntry(entryId, kind, entryName));
}
} else {
SVNWCProperties.appendPropertyDeleted(entryName, dst);
if (dirContents != null) {
dirContents.remove(entryName);
}
}
} finally {
SVNFileUtil.closeFile(dst);
}
}
public void writeChangeEntry(OutputStream changesFile, FSPathChange pathChange, boolean includeNodeKind) throws SVNException, IOException {
FSPathChangeKind changeKind = pathChange.getChangeKind();
if (!(changeKind == FSPathChangeKind.FS_PATH_CHANGE_ADD || changeKind == FSPathChangeKind.FS_PATH_CHANGE_DELETE || changeKind == FSPathChangeKind.FS_PATH_CHANGE_MODIFY
|| changeKind == FSPathChangeKind.FS_PATH_CHANGE_REPLACE || changeKind == FSPathChangeKind.FS_PATH_CHANGE_RESET)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Invalid change type");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
String changeString = changeKind.toString();
if (includeNodeKind) {
changeString += "-" + pathChange.getKind().toString();
}
String idString = null;
if (pathChange.getRevNodeId() != null) {
idString = pathChange.getRevNodeId().toString();
} else {
idString = FSPathChangeKind.ACTION_RESET;
}
String output = idString + " " + changeString + " " + SVNProperty.toString(pathChange.isTextModified()) + " " + SVNProperty.toString(pathChange.arePropertiesModified()) + " "
+ pathChange.getPath() + "\n";
changesFile.write(output.getBytes("UTF-8"));
String copyfromPath = pathChange.getCopyPath();
long copyfromRevision = pathChange.getCopyRevision();
if (copyfromPath != null && copyfromRevision != SVNRepository.INVALID_REVISION) {
String copyfromLine = copyfromRevision + " " + copyfromPath;
changesFile.write(copyfromLine.getBytes("UTF-8"));
}
changesFile.write("\n".getBytes("UTF-8"));
}
public long writeFinalChangedPathInfo(final CountingOutputStream protoFile) throws SVNException, IOException {
long offset = protoFile.getPosition();
Map changedPaths = getChangedPaths();
boolean includeNodeKind = getOwner().getDBFormat() >= FSFS.MIN_KIND_IN_CHANGED_FORMAT;
for (Iterator paths = changedPaths.keySet().iterator(); paths.hasNext();) {
String path = (String) paths.next();
FSPathChange change = (FSPathChange) changedPaths.get(path);
FSID id = change.getRevNodeId();
if (change.getChangeKind() != FSPathChangeKind.FS_PATH_CHANGE_DELETE && !id.isTxn()) {
FSRevisionNode revNode = getOwner().getRevisionNode(id);
change.setRevNodeId(revNode.getId());
}
writeChangeEntry(protoFile, change, includeNodeKind);
}
return offset;
}
public String[] readNextIDs() throws SVNException {
String[] ids = new String[2];
String idsToParse = null;
FSFile idsFile = new FSFile(getOwner().getNextIDsFile(myTxnID));
try {
idsToParse = idsFile.readLine(FSRepositoryUtil.MAX_KEY_SIZE * 2 + 3);
} finally {
idsFile.close();
}
int delimiterInd = idsToParse.indexOf(' ');
if (delimiterInd == -1) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "next-ids file corrupt");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
ids[0] = idsToParse.substring(0, delimiterInd);
ids[1] = idsToParse.substring(delimiterInd + 1);
return ids;
}
public void writeFinalCurrentFile(long newRevision, String startNodeId, String startCopyId) throws SVNException, IOException {
if (getOwner().getDBFormat() >= FSFS.MIN_NO_GLOBAL_IDS_FORMAT) {
getOwner().writeCurrentFile(newRevision, null, null);
return;
}
String[] txnIds = readNextIDs();
String txnNodeId = txnIds[0];
String txnCopyId = txnIds[1];
String newNodeId = FSTransactionRoot.addKeys(startNodeId, txnNodeId);
String newCopyId = FSTransactionRoot.addKeys(startCopyId, txnCopyId);
getOwner().writeCurrentFile(newRevision, newNodeId, newCopyId);
}
public FSID writeFinalRevision(FSID newId, final CountingOutputStream protoFile, long revision, FSID id,
String startNodeId, String startCopyId, Collection<FSRepresentation> representations) throws SVNException, IOException {
newId = null;
if (!id.isTxn()) {
return newId;
}
FSFS owner = getOwner();
FSRevisionNode revNode = owner.getRevisionNode(id);
if (revNode.getType() == SVNNodeKind.DIR) {
Map namesToEntries = revNode.getDirEntries(owner);
for (Iterator entries = namesToEntries.values().iterator(); entries.hasNext();) {
FSEntry dirEntry = (FSEntry) entries.next();
newId = writeFinalRevision(newId, protoFile, revision, dirEntry.getId(),
startNodeId, startCopyId, representations);
if (newId != null && newId.getRevision() == revision) {
dirEntry.setId(newId);
}
}
if (revNode.getTextRepresentation() != null && revNode.getTextRepresentation().isTxn()) {
SVNProperties unparsedEntries = unparseDirEntries(namesToEntries);
FSRepresentation textRep = revNode.getTextRepresentation();
textRep.setTxnId(null);
textRep.setRevision(revision);
try {
textRep.setOffset(protoFile.getPosition());
final MessageDigest checksum = MessageDigest.getInstance("MD5");
long size = writeHashRepresentation(unparsedEntries, protoFile, checksum);
String hexDigest = SVNFileUtil.toHexDigest(checksum);
textRep.setSize(size);
textRep.setMD5HexDigest(hexDigest);
textRep.setExpandedSize(textRep.getSize());
} catch (NoSuchAlgorithmException nsae) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
"MD5 implementation not found: {0}", nsae.getLocalizedMessage());
SVNErrorManager.error(err, nsae, SVNLogType.FSFS);
}
}
} else {
if (revNode.getTextRepresentation() != null && revNode.getTextRepresentation().isTxn()) {
FSRepresentation textRep = revNode.getTextRepresentation();
textRep.setTxnId(null);
textRep.setRevision(revision);
}
}
if (revNode.getPropsRepresentation() != null && revNode.getPropsRepresentation().isTxn()) {
SVNProperties props = revNode.getProperties(owner);
FSRepresentation propsRep = revNode.getPropsRepresentation();
try {
propsRep.setOffset(protoFile.getPosition());
final MessageDigest checksum = MessageDigest.getInstance("MD5");
long size = writeHashRepresentation(props, protoFile, checksum);
String hexDigest = SVNFileUtil.toHexDigest(checksum);
propsRep.setSize(size);
propsRep.setMD5HexDigest(hexDigest);
propsRep.setTxnId(null);
propsRep.setRevision(revision);
propsRep.setExpandedSize(size);
} catch (NoSuchAlgorithmException nsae) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
"MD5 implementation not found: {0}", nsae.getLocalizedMessage());
SVNErrorManager.error(err, nsae, SVNLogType.FSFS);
}
}
long myOffset = protoFile.getPosition();
String myNodeId = null;
String nodeId = revNode.getId().getNodeID();
if (nodeId.startsWith("_")) {
if (getOwner().getDBFormat() >= FSFS.MIN_NO_GLOBAL_IDS_FORMAT) {
myNodeId = nodeId.substring(1) + "-" + revision;
} else {
myNodeId = FSTransactionRoot.addKeys(startNodeId, nodeId.substring(1));
}
} else {
myNodeId = nodeId;
}
String myCopyId = null;
String copyId = revNode.getId().getCopyID();
if (copyId.startsWith("_")) {
if (getOwner().getDBFormat() >= FSFS.MIN_NO_GLOBAL_IDS_FORMAT) {
myCopyId = copyId.substring(1) + "-" + revision;
} else {
myCopyId = FSTransactionRoot.addKeys(startCopyId, copyId.substring(1));
}
} else {
myCopyId = copyId;
}
if (revNode.getCopyRootRevision() == SVNRepository.INVALID_REVISION) {
revNode.setCopyRootRevision(revision);
}
newId = FSID.createRevId(myNodeId, myCopyId, revision, myOffset);
revNode.setId(newId);
getOwner().writeTxnNodeRevision(protoFile, revNode);
if (representations != null && revNode.getTextRepresentation() != null && revNode.getType() == SVNNodeKind.FILE &&
revNode.getTextRepresentation().getRevision() == revision) {
representations.add(revNode.getTextRepresentation());
}
revNode.setIsFreshTxnRoot(false);
getOwner().putTxnRevisionNode(id, revNode);
return newId;
}
public FSRevisionNode cloneChild(FSRevisionNode parent, String parentPath, String childName, String copyId, boolean isParentCopyRoot) throws SVNException {
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);
}
if (!SVNPathUtil.isSinglePathComponent(childName)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_SINGLE_PATH_COMPONENT, "Attempted to make a child clone with an illegal name ''{0}''", childName);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSRevisionNode childNode = parent.getChildDirNode(childName, getOwner());
FSID newNodeId = null;
if (childNode.getId().isTxn()) {
newNodeId = childNode.getId();
} else {
if (isParentCopyRoot) {
childNode.setCopyRootPath(parent.getCopyRootPath());
childNode.setCopyRootRevision(parent.getCopyRootRevision());
}
childNode.setCopyFromPath(null);
childNode.setCopyFromRevision(SVNRepository.INVALID_REVISION);
childNode.setPredecessorId(childNode.getId());
if (childNode.getCount() != -1) {
childNode.setCount(childNode.getCount() + 1);
}
childNode.setCreatedPath(SVNPathUtil.getAbsolutePath(SVNPathUtil.append(parentPath, childName)));
newNodeId = createSuccessor(childNode.getId(), childNode, copyId);
setEntry(parent, childName, newNodeId, childNode.getType());
}
return getOwner().getRevisionNode(newNodeId);
}
public File getTransactionRevNodePropsFile(FSID id) {
return new File(getOwner().getTransactionDir(id.getTxnID()), FSFS.PATH_PREFIX_NODE + id.getNodeID() + "." + id.getCopyID() + FSFS.TXN_PATH_EXT_PROPS);
}
public File getTransactionRevNodeChildrenFile(FSID id) {
return new File(getOwner().getTransactionDir(id.getTxnID()), FSFS.PATH_PREFIX_NODE + id.getNodeID() + "." + id.getCopyID() + FSFS.TXN_PATH_EXT_CHILDREN);
}
public File getTransactionProtoRevFile() {
if (myTxnRevFile == null) {
myTxnRevFile = getOwner().getTransactionProtoRevFile(myTxnID);
}
return myTxnRevFile;
}
public File getTransactionChangesFile() {
if (myTxnChangesFile == null) {
myTxnChangesFile = new File(getOwner().getTransactionDir(myTxnID), "changes");
}
return myTxnChangesFile;
}
public String getNewTxnNodeId() throws SVNException {
String[] curIds = readNextIDs();
String curNodeId = curIds[0];
String curCopyId = curIds[1];
String nextNodeId = FSRepositoryUtil.generateNextKey(curNodeId);
getOwner().writeNextIDs(getTxnID(), nextNodeId, curCopyId);
return "_" + curNodeId;
}
private long writeHashRepresentation(SVNProperties hashContents, OutputStream protoFile, MessageDigest digest) throws IOException, SVNException {
HashRepresentationStream targetFile = new HashRepresentationStream(protoFile, digest);
String header = FSRepresentation.REP_PLAIN + "\n";
protoFile.write(header.getBytes("UTF-8"));
SVNWCProperties.setProperties(hashContents, targetFile, SVNWCProperties.SVN_HASH_TERMINATOR);
String trailer = FSRepresentation.REP_TRAILER + "\n";
protoFile.write(trailer.getBytes("UTF-8"));
return targetFile.mySize;
}
private static String addKeys(String key1, String key2) {
int i1 = key1.length() - 1;
int i2 = key2.length() - 1;
int carry = 0;
int val;
StringBuffer result = new StringBuffer();
while (i1 >= 0 || i2 >= 0 || carry > 0) {
val = carry;
if (i1 >= 0) {
val += key1.charAt(i1) <= '9' ? key1.charAt(i1) - '0' : key1.charAt(i1) - 'a' + 10;
}
if (i2 >= 0) {
val += key2.charAt(i2) <= '9' ? key2.charAt(i2) - '0' : key2.charAt(i2) - 'a' + 10;
}
carry = val / 36;
val = val % 36;
char sym = val <= 9 ? (char) ('0' + val) : (char) (val - 10 + 'a');
result.append(sym);
if (i1 >= 0) {
--i1;
}
if (i2 >= 0) {
--i2;
}
}
return result.reverse().toString();
}
private static class HashRepresentationStream extends OutputStream {
long mySize;
MessageDigest myChecksum;
OutputStream myProtoFile;
public HashRepresentationStream(OutputStream protoFile, MessageDigest digest) {
super();
mySize = 0;
myChecksum = digest;
myProtoFile = protoFile;
}
public void write(int b) throws IOException {
myProtoFile.write(b);
if (myChecksum != null) {
myChecksum.update((byte) b);
}
mySize++;
}
public void write(byte[] b, int off, int len) throws IOException {
myProtoFile.write(b, off, len);
if (myChecksum != null) {
myChecksum.update(b, off, len);
}
mySize += len;
}
public void write(byte[] b) throws IOException {
myProtoFile.write(b);
if (myChecksum != null) {
myChecksum.update(b);
}
mySize += b.length;
}
}
}