/* * ==================================================================== * 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.util.Collections; 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.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.SVNRepository; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class FSRevisionNode { // rev-node files keywords public static final String HEADER_ID = "id"; public static final String HEADER_TYPE = "type"; public static final String HEADER_COUNT = "count"; public static final String HEADER_PROPS = "props"; public static final String HEADER_TEXT = "text"; public static final String HEADER_CPATH = "cpath"; public static final String HEADER_PRED = "pred"; public static final String HEADER_COPYFROM = "copyfrom"; public static final String HEADER_COPYROOT = "copyroot"; public static final String HEADER_IS_FRESH_TXN_ROOT = "is-fresh-txn-root"; public static final String HEADER_MERGE_INFO_COUNT = "minfo-cnt"; public static final String HEADER_MERGE_INFO_HERE = "minfo-here"; // id: a.b.r<revID>/offset private FSID myId; // type: 'dir' or 'file' private SVNNodeKind myType; // count: count of revs since base private long myCount; // (_)a.(_)b.tx-y // pred: a.b.r<revID>/offset private FSID myPredecessorId; // text: <rev> <offset> <length> <size> <digest> private FSRepresentation myTextRepresentation; // props: <rev> <offset> <length> <size> <digest> private FSRepresentation myPropsRepresentation; // cpath: <path> private String myCreatedPath; // copyfrom: <revID> <path> private long myCopyFromRevision; private String myCopyFromPath; // copyroot: <revID> <created-path> private long myCopyRootRevision; private String myCopyRootPath; // for only node-revs representing dirs private Map myDirContents; //in case of txn root: whether it wasn't //changed yet (fresh) or was private boolean myIsFreshTxnRoot; private FSID myFreshRootPredecessorId; private long myMergeInfoCount; private boolean myHasMergeInfo; public void setId(FSID revNodeID) { myId = revNodeID; } public void setType(SVNNodeKind nodeKind) { myType = nodeKind; } public void setCount(long count) { myCount = count; } public void setPredecessorId(FSID predRevNodeId) { myPredecessorId = predRevNodeId; } public void setTextRepresentation(FSRepresentation textRepr) { myTextRepresentation = textRepr; } public void setPropsRepresentation(FSRepresentation propsRepr) { myPropsRepresentation = propsRepr; } public void setCreatedPath(String cpath) { myCreatedPath = cpath; } public void setCopyFromRevision(long copyFromRev) { myCopyFromRevision = copyFromRev; } public void setCopyFromPath(String copyFromPath) { myCopyFromPath = copyFromPath; } public void setCopyRootRevision(long copyRootRev) { myCopyRootRevision = copyRootRev; } public void setCopyRootPath(String copyRootPath) { myCopyRootPath = copyRootPath; } public void setMergeInfoCount(long mergeInfoCount) { myMergeInfoCount = mergeInfoCount; } public void setHasMergeInfo(boolean hasMergeInfo) { myHasMergeInfo = hasMergeInfo; } public FSID getId() { return myId; } public SVNNodeKind getType() { return myType; } public long getCount() { return myCount; } public FSID getPredecessorId() { return myPredecessorId; } // text public FSRepresentation getTextRepresentation() { return myTextRepresentation; } // props public FSRepresentation getPropsRepresentation() { return myPropsRepresentation; } public String getCreatedPath() { return myCreatedPath; } public long getCreatedRevision() { if (myFreshRootPredecessorId != null) { return myFreshRootPredecessorId.getRevision(); } return myId.getRevision(); } public long getCopyFromRevision() { return myCopyFromRevision; } public String getCopyFromPath() { return myCopyFromPath; } public long getCopyRootRevision() { return myCopyRootRevision; } public String getCopyRootPath() { return myCopyRootPath; } public static FSRevisionNode dumpRevisionNode(FSRevisionNode revNode) { FSRevisionNode clone = new FSRevisionNode(); clone.setId(revNode.getId()); if (revNode.getPredecessorId() != null) { clone.setPredecessorId(revNode.getPredecessorId()); } clone.setType(revNode.getType()); clone.setCopyFromPath(revNode.getCopyFromPath()); clone.setCopyFromRevision(revNode.getCopyFromRevision()); clone.setCopyRootPath(revNode.getCopyRootPath()); clone.setCopyRootRevision(revNode.getCopyRootRevision()); clone.setCount(revNode.getCount()); clone.setCreatedPath(revNode.getCreatedPath()); if (revNode.getPropsRepresentation() != null) { clone.setPropsRepresentation(new FSRepresentation(revNode.getPropsRepresentation())); } if (revNode.getTextRepresentation() != null) { clone.setTextRepresentation(new FSRepresentation(revNode.getTextRepresentation())); } clone.setMergeInfoCount(revNode.getMergeInfoCount()); clone.setHasMergeInfo(revNode.hasMergeInfo()); return clone; } protected Map getDirContents() { return myDirContents; } public void setDirContents(Map dirContents) { myDirContents = dirContents; } public boolean hasMergeInfo() { return myHasMergeInfo; } public long getMergeInfoCount() { return myMergeInfoCount; } public boolean hasDescendantsWithMergeInfo() { if (myType != SVNNodeKind.DIR) { return false; } if (myMergeInfoCount > 1) { return true; } else if (myMergeInfoCount == 1 && !myHasMergeInfo) { return true; } return false; } public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("{["); buffer.append("ID:"); buffer.append(myId); buffer.append("]["); buffer.append("text representation:"); buffer.append(myTextRepresentation); buffer.append("]["); buffer.append("Node kind:"); buffer.append(myType); buffer.append("]}"); return buffer.toString(); } public static FSRevisionNode fromMap(Map headers) throws SVNException { FSRevisionNode revNode = new FSRevisionNode(); // Read the rev-node id. String revNodeId = (String) headers.get(FSRevisionNode.HEADER_ID); if (revNodeId == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Missing id field in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } FSID revnodeID = FSID.fromString(revNodeId); if (revnodeID == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Corrupted node-id in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } revNode.setId(revnodeID); // Read the type. SVNNodeKind nodeKind = SVNNodeKind.parseKind((String) headers.get(FSRevisionNode.HEADER_TYPE)); if (nodeKind == SVNNodeKind.NONE || nodeKind == SVNNodeKind.UNKNOWN) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Missing kind field in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } revNode.setType(nodeKind); // Read the 'count' field. String countString = (String) headers.get(FSRevisionNode.HEADER_COUNT); if (countString == null) { revNode.setCount(0); } else { long cnt = -1; try { cnt = Long.parseLong(countString); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Corrupted count field in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } revNode.setCount(cnt); } // Get the properties location (if any). String propsRepr = (String) headers.get(FSRevisionNode.HEADER_PROPS); if (propsRepr != null) { parseRepresentationHeader(propsRepr, revNode, revnodeID.getTxnID(), false, true); } // Get the data location (if any). String textRepr = (String) headers.get(FSRevisionNode.HEADER_TEXT); if (textRepr != null) { parseRepresentationHeader(textRepr, revNode, revnodeID.getTxnID(), true, nodeKind == SVNNodeKind.DIR); } // Get the created path. String cpath = (String) headers.get(FSRevisionNode.HEADER_CPATH); if (cpath == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Missing cpath in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } revNode.setCreatedPath(cpath); // Get the predecessor rev-node id (if any). String predId = (String) headers.get(FSRevisionNode.HEADER_PRED); if (predId != null) { FSID predRevNodeId = FSID.fromString(predId); if (predRevNodeId == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Corrupted predecessor node-id in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } revNode.setPredecessorId(predRevNodeId); } // Get the copyroot. String copyroot = (String) headers.get(FSRevisionNode.HEADER_COPYROOT); if (copyroot == null) { revNode.setCopyRootPath(revNode.getCreatedPath()); revNode.setCopyRootRevision(revNode.getCreatedRevision()); } else { parseCopyRoot(copyroot, revNode); } // Get the copyfrom. String copyfrom = (String) headers.get(FSRevisionNode.HEADER_COPYFROM); if (copyfrom == null) { revNode.setCopyFromPath(null); revNode.setCopyFromRevision(SVNRepository.INVALID_REVISION); } else { parseCopyFrom(copyfrom, revNode); } revNode.myIsFreshTxnRoot = headers.containsKey(HEADER_IS_FRESH_TXN_ROOT); String mergeInfoCountStr = (String) headers.get(HEADER_MERGE_INFO_COUNT); if (mergeInfoCountStr == null) { revNode.myMergeInfoCount = 0; } else { try { revNode.myMergeInfoCount = Long.parseLong(mergeInfoCountStr); } catch (NumberFormatException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Corrupted mergeinfo count in node-rev"); SVNErrorManager.error(err, e, SVNLogType.FSFS); } } revNode.myHasMergeInfo = headers.containsKey(HEADER_MERGE_INFO_HERE); return revNode; } public static void parseRepresentationHeader(String representation, FSRevisionNode revNode, String txnId, boolean isData, boolean mutableRepTuncated) throws SVNException { if (revNode == null) { return; } FSRepresentation rep = new FSRepresentation(); int delimiterInd = representation.indexOf(' '); String revision = null; if (delimiterInd == -1) { revision = representation; } else { revision = representation.substring(0, delimiterInd); } long rev = -1; try { rev = Long.parseLong(revision); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } rep.setRevision(rev); if (FSRepository.isInvalidRevision(rep.getRevision())) { rep.setTxnId(txnId); if (isData) { revNode.setTextRepresentation(rep); } else { revNode.setPropsRepresentation(rep); } if (mutableRepTuncated) { return; } } representation = representation.substring(delimiterInd + 1); delimiterInd = representation.indexOf(' '); if (delimiterInd == -1) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } String repOffset = representation.substring(0, delimiterInd); long offset = -1; try { offset = Long.parseLong(repOffset); if (offset < 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } rep.setOffset(offset); representation = representation.substring(delimiterInd + 1); delimiterInd = representation.indexOf(' '); if (delimiterInd == -1) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } String repSize = representation.substring(0, delimiterInd); long size = -1; try { size = Long.parseLong(repSize); if (size < 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } rep.setSize(size); representation = representation.substring(delimiterInd + 1); delimiterInd = representation.indexOf(' '); if (delimiterInd == -1) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } String repExpandedSize = representation.substring(0, delimiterInd); long expandedSize = -1; try { expandedSize = Long.parseLong(repExpandedSize); if (expandedSize < 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } rep.setExpandedSize(expandedSize); representation = representation.substring(delimiterInd + 1); delimiterInd = representation.indexOf(' '); String hexMD5Digest = null; if (delimiterInd == -1) { hexMD5Digest = representation; } else { hexMD5Digest = representation.substring(0, delimiterInd); } if (hexMD5Digest.length() != 32 || SVNFileUtil.fromHexDigest(hexMD5Digest) == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } rep.setMD5HexDigest(hexMD5Digest); if (isData) { revNode.setTextRepresentation(rep); } else { revNode.setPropsRepresentation(rep); } if (delimiterInd == -1) { return; } representation = representation.substring(delimiterInd + 1); delimiterInd = representation.indexOf(' '); String hexSHA1Digest = null; if (delimiterInd == -1) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } hexSHA1Digest = representation.substring(0, delimiterInd); if (hexSHA1Digest.length() != 40 || SVNFileUtil.fromHexDigest(hexSHA1Digest) == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed text rep offset line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } rep.setSHA1HexDigest(hexSHA1Digest); representation = representation.substring(delimiterInd + 1); delimiterInd = representation.indexOf(' '); String uniquifier = null; if (delimiterInd != -1) { uniquifier = representation.substring(0, delimiterInd); } else { uniquifier = representation; } rep.setUniquifier(uniquifier); } private static void parseCopyFrom(String copyfrom, FSRevisionNode revNode) throws SVNException { if (copyfrom == null || copyfrom.length() == 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed copyfrom line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } int delimiterInd = copyfrom.indexOf(' '); if (delimiterInd == -1) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed copyfrom line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } String copyfromRev = copyfrom.substring(0, delimiterInd); String copyfromPath = copyfrom.substring(delimiterInd + 1); long rev = -1; try { rev = Long.parseLong(copyfromRev); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed copyfrom line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } revNode.setCopyFromRevision(rev); revNode.setCopyFromPath(copyfromPath); } private static void parseCopyRoot(String copyroot, FSRevisionNode revNode) throws SVNException { if (copyroot == null || copyroot.length() == 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed copyroot line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } int delimiterInd = copyroot.indexOf(' '); if (delimiterInd == -1) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed copyroot line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } String copyrootRev = copyroot.substring(0, delimiterInd); String copyrootPath = copyroot.substring(delimiterInd + 1); long rev = -1; try { rev = Long.parseLong(copyrootRev); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed copyroot line in node-rev"); SVNErrorManager.error(err, SVNLogType.FSFS); } revNode.setCopyRootRevision(rev); revNode.setCopyRootPath(copyrootPath); } public FSRevisionNode getChildDirNode(String childName, FSFS fsfsOwner) throws SVNException { if (!SVNPathUtil.isSinglePathComponent(childName)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_SINGLE_PATH_COMPONENT, "Attempted to open node with an illegal name ''{0}''", childName); SVNErrorManager.error(err, SVNLogType.FSFS); } Map entries = getDirEntries(fsfsOwner); FSEntry entry = entries != null ? (FSEntry) entries.get(childName) : null; if (entry == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Attempted to open non-existent child node ''{0}''", childName); SVNErrorManager.error(err, SVNLogType.FSFS); } return fsfsOwner.getRevisionNode(entry.getId()); } public Map getDirEntries(FSFS fsfsOwner) throws SVNException { if (getType() != SVNNodeKind.DIR) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_DIRECTORY, "Can't get entries of non-directory"); SVNErrorManager.error(err, SVNLogType.FSFS); } Map dirContents = getDirContents(); if (dirContents == null) { dirContents = fsfsOwner.getDirContents(this); setDirContents(dirContents); } return Collections.unmodifiableMap(dirContents); } public SVNProperties getProperties(FSFS fsfsOwner) throws SVNException { return fsfsOwner.getProperties(this); } public FSRepresentation chooseDeltaBase(FSFS fsfsOwner) throws SVNException { if (getCount() == 0) { return null; } long count = getCount(); count = count & (count - 1); FSRevisionNode baseNode = this; while ((count++) < getCount()) { baseNode = fsfsOwner.getRevisionNode(baseNode.getPredecessorId()); } return baseNode.getTextRepresentation(); } public String getFileMD5Checksum() throws SVNException { if (getType() != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FILE, "Attempted to get checksum of a *non*-file node"); SVNErrorManager.error(err, SVNLogType.FSFS); } return getTextRepresentation() != null ? getTextRepresentation().getMD5HexDigest() : ""; } public String getFileSHA1Checksum() throws SVNException { if (getType() != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FILE, "Attempted to get checksum of a *non*-file node"); SVNErrorManager.error(err, SVNLogType.FSFS); } return getTextRepresentation() != null ? getTextRepresentation().getSHA1HexDigest() : ""; } public long getFileLength() throws SVNException { if (getType() != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FILE, "Attempted to get length of a *non*-file node"); SVNErrorManager.error(err, SVNLogType.FSFS); } return getTextRepresentation() != null ? getTextRepresentation().getExpandedSize() : 0; } public void setIsFreshTxnRoot(boolean isFreshTxnRoot) { myIsFreshTxnRoot = isFreshTxnRoot; } public boolean isFreshTxnRoot() { return myIsFreshTxnRoot; } public void setFreshRootPredecessorId(FSID freshRootPredecessorId) { myFreshRootPredecessorId = freshRootPredecessorId; } }