001: /*
002: * ====================================================================
003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
004: *
005: * This software is licensed as described in the file COPYING, which
006: * you should have received as part of this distribution. The terms
007: * are also available at http://svnkit.com/license.html
008: * If newer versions of this license are posted there, you may use a
009: * newer version instead, at your option.
010: * ====================================================================
011: */
012: package org.tmatesoft.svn.core.internal.io.fs;
013:
014: import java.io.File;
015: import java.io.IOException;
016: import java.io.OutputStream;
017: import java.security.MessageDigest;
018: import java.security.NoSuchAlgorithmException;
019: import java.util.Date;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023:
024: import org.tmatesoft.svn.core.SVNErrorCode;
025: import org.tmatesoft.svn.core.SVNErrorMessage;
026: import org.tmatesoft.svn.core.SVNException;
027: import org.tmatesoft.svn.core.SVNNodeKind;
028: import org.tmatesoft.svn.core.SVNProperty;
029: import org.tmatesoft.svn.core.SVNRevisionProperty;
030: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
031: import org.tmatesoft.svn.core.internal.util.SVNTimeUtil;
032: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
033: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
034: import org.tmatesoft.svn.core.internal.wc.SVNProperties;
035:
036: /**
037: * @version 1.1.1
038: * @author TMate Software Ltd.
039: */
040: public class FSTransactionRoot extends FSRoot {
041:
042: public static final int SVN_FS_TXN_CHECK_OUT_OF_DATENESS = 0x00001;
043: public static final int SVN_FS_TXN_CHECK_LOCKS = 0x00002;
044: private static final int MAX_KEY_SIZE = 200;
045:
046: private String myTxnID;
047: private int myTxnFlags;
048: private File myTxnChangesFile;
049: private File myTxnRevFile;
050:
051: public FSTransactionRoot(FSFS owner, String txnID, int flags) {
052: super (owner);
053: myTxnID = txnID;
054: myTxnFlags = flags;
055:
056: }
057:
058: public FSCopyInheritance getCopyInheritance(FSParentPath child)
059: throws SVNException {
060: if (child == null || child.getParent() == null
061: || myTxnID == null) {
062: SVNErrorMessage err = SVNErrorMessage.create(
063: SVNErrorCode.UNKNOWN,
064: "FATAL error: invalid txn name or child");
065: SVNErrorManager.error(err);
066: }
067: FSID childID = child.getRevNode().getId();
068: FSID parentID = child.getParent().getRevNode().getId();
069: String childCopyID = childID.getCopyID();
070: String parentCopyID = parentID.getCopyID();
071:
072: if (childID.isTxn()) {
073: return new FSCopyInheritance(
074: FSCopyInheritance.COPY_ID_INHERIT_SELF, null);
075: }
076:
077: FSCopyInheritance copyInheritance = new FSCopyInheritance(
078: FSCopyInheritance.COPY_ID_INHERIT_PARENT, null);
079:
080: if (childCopyID.compareTo("0") == 0) {
081: return copyInheritance;
082: }
083:
084: if (childCopyID.compareTo(parentCopyID) == 0) {
085: return copyInheritance;
086: }
087:
088: long copyrootRevision = child.getRevNode()
089: .getCopyRootRevision();
090: String copyrootPath = child.getRevNode().getCopyRootPath();
091:
092: FSRoot copyrootRoot = getOwner().createRevisionRoot(
093: copyrootRevision);
094: FSRevisionNode copyrootNode = copyrootRoot
095: .getRevisionNode(copyrootPath);
096: FSID copyrootID = copyrootNode.getId();
097: if (copyrootID.compareTo(childID) == -1) {
098: return copyInheritance;
099: }
100:
101: String idPath = child.getRevNode().getCreatedPath();
102: if (idPath.compareTo(child.getAbsPath()) == 0) {
103: copyInheritance
104: .setStyle(FSCopyInheritance.COPY_ID_INHERIT_SELF);
105: return copyInheritance;
106: }
107:
108: copyInheritance.setStyle(FSCopyInheritance.COPY_ID_INHERIT_NEW);
109: copyInheritance.setCopySourcePath(idPath);
110: return copyInheritance;
111: }
112:
113: public FSRevisionNode getRootRevisionNode() throws SVNException {
114: if (myRootRevisionNode == null) {
115: FSTransactionInfo txn = getTxn();
116: myRootRevisionNode = getOwner().getRevisionNode(
117: txn.getRootID());
118: }
119: return myRootRevisionNode;
120: }
121:
122: public FSRevisionNode getTxnBaseRootNode() throws SVNException {
123: FSTransactionInfo txn = getTxn();
124: FSRevisionNode baseRootNode = getOwner().getRevisionNode(
125: txn.getBaseID());
126: return baseRootNode;
127: }
128:
129: public FSTransactionInfo getTxn() throws SVNException {
130: FSID rootID = FSID.createTxnId("0", "0", myTxnID);
131: FSRevisionNode revNode = getOwner().getRevisionNode(rootID);
132: FSTransactionInfo txn = new FSTransactionInfo(revNode.getId(),
133: revNode.getPredecessorId());
134: return txn;
135: }
136:
137: public Map getChangedPaths() throws SVNException {
138: FSFile file = getOwner().getTransactionChangesFile(myTxnID);
139: try {
140: return fetchAllChanges(file, false);
141: } finally {
142: file.close();
143: }
144: }
145:
146: public int getTxnFlags() {
147: return myTxnFlags;
148: }
149:
150: public void setTxnFlags(int txnFlags) {
151: myTxnFlags = txnFlags;
152: }
153:
154: public String getTxnID() {
155: return myTxnID;
156: }
157:
158: public Map unparseDirEntries(Map entries) {
159: Map unparsedEntries = new HashMap();
160: for (Iterator names = entries.keySet().iterator(); names
161: .hasNext();) {
162: String name = (String) names.next();
163: FSEntry dirEntry = (FSEntry) entries.get(name);
164: String unparsedVal = dirEntry.toString();
165: unparsedEntries.put(name, unparsedVal);
166: }
167: return unparsedEntries;
168: }
169:
170: public static FSTransactionInfo beginTransaction(long baseRevision,
171: int flags, FSFS owner) throws SVNException {
172: FSTransactionInfo txn = createTxn(baseRevision, owner);
173: String commitTime = SVNTimeUtil.formatDate(new Date(System
174: .currentTimeMillis()));
175: owner.setTransactionProperty(txn.getTxnId(),
176: SVNRevisionProperty.DATE, commitTime);
177:
178: if ((flags & SVN_FS_TXN_CHECK_OUT_OF_DATENESS) != 0) {
179: owner.setTransactionProperty(txn.getTxnId(),
180: SVNProperty.TXN_CHECK_OUT_OF_DATENESS, SVNProperty
181: .toString(true));
182: }
183:
184: if ((flags & FSTransactionRoot.SVN_FS_TXN_CHECK_LOCKS) != 0) {
185: owner.setTransactionProperty(txn.getTxnId(),
186: SVNProperty.TXN_CHECK_LOCKS, SVNProperty
187: .toString(true));
188: }
189:
190: return txn;
191: }
192:
193: private static FSTransactionInfo createTxn(long baseRevision,
194: FSFS owner) throws SVNException {
195: String txnID = createTxnDir(baseRevision, owner);
196: FSTransactionInfo txn = new FSTransactionInfo(baseRevision,
197: txnID);
198: FSRevisionRoot root = owner.createRevisionRoot(baseRevision);
199: FSRevisionNode rootNode = root.getRootRevisionNode();
200: owner.createNewTxnNodeRevisionFromRevision(txnID, rootNode);
201: SVNFileUtil.createEmptyFile(new File(owner
202: .getTransactionDir(txn.getTxnId()), FSFS.TXN_PATH_REV));
203: SVNFileUtil.createEmptyFile(new File(owner
204: .getTransactionDir(txn.getTxnId()), "changes"));
205: owner.writeNextIDs(txnID, "0", "0");
206: return txn;
207: }
208:
209: private static String createTxnDir(long revision, FSFS owner)
210: throws SVNException {
211: File parent = owner.getTransactionsParentDir();
212: File uniquePath = null;
213:
214: for (int i = 1; i < 99999; i++) {
215: uniquePath = new File(parent, revision + "-" + i
216: + FSFS.TXN_PATH_EXT);
217: if (!uniquePath.exists() && uniquePath.mkdirs()) {
218: return revision + "-" + i;
219: }
220: }
221: SVNErrorMessage err = SVNErrorMessage
222: .create(
223: SVNErrorCode.IO_UNIQUE_NAMES_EXHAUSTED,
224: "Unable to create transaction directory in ''{0}'' for revision {1,number,integer}",
225: new Object[] { parent, new Long(revision) });
226: SVNErrorManager.error(err);
227: return null;
228: }
229:
230: public void deleteEntry(FSRevisionNode parent, String entryName)
231: throws SVNException {
232: if (parent.getType() != SVNNodeKind.DIR) {
233: SVNErrorMessage err = SVNErrorMessage
234: .create(
235: SVNErrorCode.FS_NOT_DIRECTORY,
236: "Attempted to delete entry ''{0}'' from *non*-directory node",
237: entryName);
238: SVNErrorManager.error(err);
239: }
240:
241: if (!parent.getId().isTxn()) {
242: SVNErrorMessage err = SVNErrorMessage
243: .create(
244: SVNErrorCode.FS_NOT_MUTABLE,
245: "Attempted to delete entry ''{0}'' from immutable directory node",
246: entryName);
247: SVNErrorManager.error(err);
248: }
249:
250: if (!SVNPathUtil.isSinglePathComponent(entryName)) {
251: SVNErrorMessage err = SVNErrorMessage
252: .create(
253: SVNErrorCode.FS_NOT_SINGLE_PATH_COMPONENT,
254: "Attempted to delete a node with an illegal name ''{0}''",
255: entryName);
256: SVNErrorManager.error(err);
257: }
258:
259: Map entries = parent.getDirEntries(getOwner());
260: FSEntry dirEntry = (FSEntry) entries.get(entryName);
261:
262: if (dirEntry == null) {
263: SVNErrorMessage err = SVNErrorMessage.create(
264: SVNErrorCode.FS_NO_SUCH_ENTRY,
265: "Delete failed--directory has no entry ''{0}''",
266: entryName);
267: SVNErrorManager.error(err);
268: }
269: getOwner().getRevisionNode(dirEntry.getId());
270: deleteEntryIfMutable(dirEntry.getId());
271: setEntry(parent, entryName, null, SVNNodeKind.UNKNOWN);
272: }
273:
274: private void deleteEntryIfMutable(FSID id) throws SVNException {
275: FSRevisionNode node = getOwner().getRevisionNode(id);
276: if (!node.getId().isTxn()) {
277: return;
278: }
279:
280: if (node.getType() == SVNNodeKind.DIR) {
281: Map entries = node.getDirEntries(getOwner());
282: for (Iterator names = entries.keySet().iterator(); names
283: .hasNext();) {
284: String name = (String) names.next();
285: FSEntry entry = (FSEntry) entries.get(name);
286: deleteEntryIfMutable(entry.getId());
287: }
288: }
289:
290: removeRevisionNode(id);
291: }
292:
293: private void removeRevisionNode(FSID id) throws SVNException {
294: FSRevisionNode node = getOwner().getRevisionNode(id);
295:
296: if (!node.getId().isTxn()) {
297: SVNErrorMessage err = SVNErrorMessage.create(
298: SVNErrorCode.FS_NOT_MUTABLE,
299: "Attempted removal of immutable node");
300: SVNErrorManager.error(err);
301: }
302:
303: if (node.getPropsRepresentation() != null
304: && node.getPropsRepresentation().isTxn()) {
305: SVNFileUtil.deleteFile(getTransactionRevNodePropsFile(id));
306: }
307:
308: if (node.getTextRepresentation() != null
309: && node.getTextRepresentation().isTxn()
310: && node.getType() == SVNNodeKind.DIR) {
311: SVNFileUtil
312: .deleteFile(getTransactionRevNodeChildrenFile(id));
313: }
314:
315: SVNFileUtil
316: .deleteFile(getOwner().getTransactionRevNodeFile(id));
317: }
318:
319: public void setProplist(FSRevisionNode node, Map properties)
320: throws SVNException {
321: if (!node.getId().isTxn()) {
322: SVNErrorMessage err = SVNErrorMessage
323: .create(
324: SVNErrorCode.FS_NOT_MUTABLE,
325: "Can't set proplist on *immutable* node-revision {0}",
326: node.getId());
327: SVNErrorManager.error(err);
328: }
329:
330: File propsFile = getTransactionRevNodePropsFile(node.getId());
331: SVNProperties.setProperties(properties, propsFile, SVNFileUtil
332: .createUniqueFile(propsFile.getParentFile(), ".props",
333: ".tmp"), SVNProperties.SVN_HASH_TERMINATOR);
334:
335: if (node.getPropsRepresentation() == null
336: || !node.getPropsRepresentation().isTxn()) {
337: FSRepresentation mutableRep = new FSRepresentation();
338: mutableRep.setTxnId(node.getId().getTxnID());
339: node.setPropsRepresentation(mutableRep);
340: node.setIsFreshTxnRoot(false);
341: getOwner().putTxnRevisionNode(node.getId(), node);
342: }
343: }
344:
345: public FSID createSuccessor(FSID oldId, FSRevisionNode newRevNode,
346: String copyId) throws SVNException {
347: if (copyId == null) {
348: copyId = oldId.getCopyID();
349: }
350: FSID id = FSID.createTxnId(oldId.getNodeID(), copyId, myTxnID);
351: newRevNode.setId(id);
352: if (newRevNode.getCopyRootPath() == null) {
353: newRevNode.setCopyRootPath(newRevNode.getCreatedPath());
354: newRevNode.setCopyRootRevision(newRevNode.getId()
355: .getRevision());
356: }
357: newRevNode.setIsFreshTxnRoot(false);
358: getOwner().putTxnRevisionNode(newRevNode.getId(), newRevNode);
359: return id;
360: }
361:
362: public void setEntry(FSRevisionNode parentRevNode,
363: String entryName, FSID entryId, SVNNodeKind kind)
364: throws SVNException {
365: if (parentRevNode.getType() != SVNNodeKind.DIR) {
366: SVNErrorMessage err = SVNErrorMessage.create(
367: SVNErrorCode.FS_NOT_DIRECTORY,
368: "Attempted to set entry in non-directory node");
369: SVNErrorManager.error(err);
370: }
371:
372: if (!parentRevNode.getId().isTxn()) {
373: SVNErrorMessage err = SVNErrorMessage.create(
374: SVNErrorCode.FS_NOT_MUTABLE,
375: "Attempted to set entry in immutable node");
376: SVNErrorManager.error(err);
377: }
378:
379: FSRepresentation textRep = parentRevNode
380: .getTextRepresentation();
381: File childrenFile = getTransactionRevNodeChildrenFile(parentRevNode
382: .getId());
383: OutputStream dst = null;
384:
385: try {
386: if (textRep == null || !textRep.isTxn()) {
387: Map entries = parentRevNode.getDirEntries(getOwner());
388: Map unparsedEntries = unparseDirEntries(entries);
389: dst = SVNFileUtil.openFileForWriting(childrenFile);
390: SVNProperties.setProperties(unparsedEntries, dst,
391: SVNProperties.SVN_HASH_TERMINATOR);
392: textRep = new FSRepresentation();
393: textRep.setRevision(FSRepository.SVN_INVALID_REVNUM);
394: textRep.setTxnId(myTxnID);
395: parentRevNode.setTextRepresentation(textRep);
396: parentRevNode.setIsFreshTxnRoot(false);
397: getOwner().putTxnRevisionNode(parentRevNode.getId(),
398: parentRevNode);
399: } else {
400: dst = SVNFileUtil
401: .openFileForWriting(childrenFile, true);
402: }
403: Map dirContents = parentRevNode.getDirContents();
404: if (entryId != null) {
405: SVNProperties.appendProperty(entryName, kind + " "
406: + entryId.toString(), dst);
407: if (dirContents != null) {
408: dirContents.put(entryName, new FSEntry(entryId,
409: kind, entryName));
410: }
411: } else {
412: SVNProperties.appendPropertyDeleted(entryName, dst);
413: if (dirContents != null) {
414: dirContents.remove(entryName);
415: }
416: }
417: } finally {
418: SVNFileUtil.closeFile(dst);
419: }
420: }
421:
422: public void writeChangeEntry(OutputStream changesFile,
423: FSPathChange pathChange) throws SVNException, IOException {
424: FSPathChangeKind changeKind = pathChange.getChangeKind();
425: if (!(changeKind == FSPathChangeKind.FS_PATH_CHANGE_ADD
426: || changeKind == FSPathChangeKind.FS_PATH_CHANGE_DELETE
427: || changeKind == FSPathChangeKind.FS_PATH_CHANGE_MODIFY
428: || changeKind == FSPathChangeKind.FS_PATH_CHANGE_REPLACE || changeKind == FSPathChangeKind.FS_PATH_CHANGE_RESET)) {
429: SVNErrorMessage err = SVNErrorMessage.create(
430: SVNErrorCode.FS_CORRUPT, "Invalid change type");
431: SVNErrorManager.error(err);
432: }
433: String changeString = changeKind.toString();
434: String idString = null;
435: if (pathChange.getRevNodeId() != null) {
436: idString = pathChange.getRevNodeId().toString();
437: } else {
438: idString = FSPathChangeKind.ACTION_RESET;
439: }
440:
441: String output = idString
442: + " "
443: + changeString
444: + " "
445: + SVNProperty.toString(pathChange.isTextModified())
446: + " "
447: + SVNProperty.toString(pathChange
448: .arePropertiesModified()) + " "
449: + pathChange.getPath() + "\n";
450: changesFile.write(output.getBytes("UTF-8"));
451:
452: String copyfromPath = pathChange.getCopyPath();
453: long copyfromRevision = pathChange.getCopyRevision();
454:
455: if (copyfromPath != null
456: && copyfromRevision != FSRepository.SVN_INVALID_REVNUM) {
457: String copyfromLine = copyfromRevision + " " + copyfromPath;
458: changesFile.write(copyfromLine.getBytes("UTF-8"));
459: }
460: changesFile.write("\n".getBytes("UTF-8"));
461: }
462:
463: public long writeFinalChangedPathInfo(final CountingStream protoFile)
464: throws SVNException, IOException {
465: long offset = protoFile.getPosition();
466: Map changedPaths = getChangedPaths();
467:
468: for (Iterator paths = changedPaths.keySet().iterator(); paths
469: .hasNext();) {
470: String path = (String) paths.next();
471: FSPathChange change = (FSPathChange) changedPaths.get(path);
472: FSID id = change.getRevNodeId();
473:
474: if (change.getChangeKind() != FSPathChangeKind.FS_PATH_CHANGE_DELETE
475: && !id.isTxn()) {
476: FSRevisionNode revNode = getOwner().getRevisionNode(id);
477: change.setRevNodeId(revNode.getId());
478: }
479:
480: writeChangeEntry(protoFile, change);
481: }
482: return offset;
483: }
484:
485: public String[] readNextIDs() throws SVNException {
486: String[] ids = new String[2];
487: String idsToParse = null;
488: FSFile idsFile = new FSFile(getOwner().getNextIDsFile(myTxnID));
489:
490: try {
491: idsToParse = idsFile
492: .readLine(FSTransactionRoot.MAX_KEY_SIZE * 2 + 3);
493: } finally {
494: idsFile.close();
495: }
496:
497: int delimiterInd = idsToParse.indexOf(' ');
498:
499: if (delimiterInd == -1) {
500: SVNErrorMessage err = SVNErrorMessage.create(
501: SVNErrorCode.FS_CORRUPT, "next-ids file corrupt");
502: SVNErrorManager.error(err);
503: }
504:
505: ids[0] = idsToParse.substring(0, delimiterInd);
506: ids[1] = idsToParse.substring(delimiterInd + 1);
507: return ids;
508: }
509:
510: public void writeFinalCurrentFile(long newRevision,
511: String startNodeId, String startCopyId)
512: throws SVNException, IOException {
513: String[] txnIds = readNextIDs();
514: String txnNodeId = txnIds[0];
515: String txnCopyId = txnIds[1];
516: String newNodeId = FSTransactionRoot.addKeys(startNodeId,
517: txnNodeId);
518: String newCopyId = FSTransactionRoot.addKeys(startCopyId,
519: txnCopyId);
520: String line = newRevision + " " + newNodeId + " " + newCopyId
521: + "\n";
522: File currentFile = getOwner().getCurrentFile();
523: File tmpCurrentFile = SVNFileUtil.createUniqueFile(currentFile
524: .getParentFile(), ".txnfile", ".tmp");
525: OutputStream currentOS = null;
526:
527: try {
528: currentOS = SVNFileUtil.openFileForWriting(tmpCurrentFile);
529: currentOS.write(line.getBytes("UTF-8"));
530: } finally {
531: SVNFileUtil.closeFile(currentOS);
532: }
533: SVNFileUtil.rename(tmpCurrentFile, currentFile);
534: }
535:
536: public FSID writeFinalRevision(FSID newId,
537: final CountingStream protoFile, long revision, FSID id,
538: String startNodeId, String startCopyId)
539: throws SVNException, IOException {
540: newId = null;
541:
542: if (!id.isTxn()) {
543: return newId;
544: }
545: FSFS owner = getOwner();
546: FSRevisionNode revNode = owner.getRevisionNode(id);
547: if (revNode.getType() == SVNNodeKind.DIR) {
548: Map namesToEntries = revNode.getDirEntries(owner);
549: for (Iterator entries = namesToEntries.values().iterator(); entries
550: .hasNext();) {
551: FSEntry dirEntry = (FSEntry) entries.next();
552: newId = writeFinalRevision(newId, protoFile, revision,
553: dirEntry.getId(), startNodeId, startCopyId);
554: if (newId != null && newId.getRevision() == revision) {
555: dirEntry.setId(newId);
556: }
557: }
558: if (revNode.getTextRepresentation() != null
559: && revNode.getTextRepresentation().isTxn()) {
560: Map unparsedEntries = unparseDirEntries(namesToEntries);
561: FSRepresentation textRep = revNode
562: .getTextRepresentation();
563: textRep.setTxnId(null);
564: textRep.setRevision(revision);
565: try {
566: textRep.setOffset(protoFile.getPosition());
567: final MessageDigest checksum = MessageDigest
568: .getInstance("MD5");
569: long size = writeHashRepresentation(
570: unparsedEntries, protoFile, checksum);
571: String hexDigest = SVNFileUtil
572: .toHexDigest(checksum);
573: textRep.setSize(size);
574: textRep.setHexDigest(hexDigest);
575: textRep.setExpandedSize(textRep.getSize());
576: } catch (NoSuchAlgorithmException nsae) {
577: SVNErrorMessage err = SVNErrorMessage.create(
578: SVNErrorCode.IO_ERROR,
579: "MD5 implementation not found: {0}", nsae
580: .getLocalizedMessage());
581: SVNErrorManager.error(err, nsae);
582: }
583: }
584: } else {
585: if (revNode.getTextRepresentation() != null
586: && revNode.getTextRepresentation().isTxn()) {
587: FSRepresentation textRep = revNode
588: .getTextRepresentation();
589: textRep.setTxnId(null);
590: textRep.setRevision(revision);
591: }
592: }
593:
594: if (revNode.getPropsRepresentation() != null
595: && revNode.getPropsRepresentation().isTxn()) {
596: Map props = revNode.getProperties(owner);
597: FSRepresentation propsRep = revNode
598: .getPropsRepresentation();
599: try {
600: propsRep.setOffset(protoFile.getPosition());
601: final MessageDigest checksum = MessageDigest
602: .getInstance("MD5");
603: long size = writeHashRepresentation(props, protoFile,
604: checksum);
605: String hexDigest = SVNFileUtil.toHexDigest(checksum);
606: propsRep.setSize(size);
607: propsRep.setHexDigest(hexDigest);
608: propsRep.setTxnId(null);
609: propsRep.setRevision(revision);
610: propsRep.setExpandedSize(size);
611: } catch (NoSuchAlgorithmException nsae) {
612: SVNErrorMessage err = SVNErrorMessage.create(
613: SVNErrorCode.IO_ERROR,
614: "MD5 implementation not found: {0}", nsae
615: .getLocalizedMessage());
616: SVNErrorManager.error(err, nsae);
617: }
618: }
619:
620: long myOffset = protoFile.getPosition();
621: String myNodeId = null;
622: String nodeId = revNode.getId().getNodeID();
623:
624: if (nodeId.startsWith("_")) {
625: myNodeId = FSTransactionRoot.addKeys(startNodeId, nodeId
626: .substring(1));
627: } else {
628: myNodeId = nodeId;
629: }
630:
631: String myCopyId = null;
632: String copyId = revNode.getId().getCopyID();
633:
634: if (copyId.startsWith("_")) {
635: myCopyId = FSTransactionRoot.addKeys(startCopyId, copyId
636: .substring(1));
637: } else {
638: myCopyId = copyId;
639: }
640:
641: if (revNode.getCopyRootRevision() == FSRepository.SVN_INVALID_REVNUM) {
642: revNode.setCopyRootRevision(revision);
643: }
644:
645: newId = FSID
646: .createRevId(myNodeId, myCopyId, revision, myOffset);
647: revNode.setId(newId);
648:
649: getOwner().writeTxnNodeRevision(protoFile, revNode);
650: revNode.setIsFreshTxnRoot(false);
651: getOwner().putTxnRevisionNode(id, revNode);
652: return newId;
653: }
654:
655: public FSRevisionNode cloneChild(FSRevisionNode parent,
656: String parentPath, String childName, String copyId,
657: boolean isParentCopyRoot) throws SVNException {
658: if (!parent.getId().isTxn()) {
659: SVNErrorMessage err = SVNErrorMessage.create(
660: SVNErrorCode.FS_NOT_MUTABLE,
661: "Attempted to clone child of non-mutable node");
662: SVNErrorManager.error(err);
663: }
664:
665: if (!SVNPathUtil.isSinglePathComponent(childName)) {
666: SVNErrorMessage err = SVNErrorMessage
667: .create(
668: SVNErrorCode.FS_NOT_SINGLE_PATH_COMPONENT,
669: "Attempted to make a child clone with an illegal name ''{0}''",
670: childName);
671: SVNErrorManager.error(err);
672: }
673:
674: FSRevisionNode childNode = parent.getChildDirNode(childName,
675: getOwner());
676: FSID newNodeId = null;
677:
678: if (childNode.getId().isTxn()) {
679: newNodeId = childNode.getId();
680: } else {
681: if (isParentCopyRoot) {
682: childNode.setCopyRootPath(parent.getCopyRootPath());
683: childNode.setCopyRootRevision(parent
684: .getCopyRootRevision());
685: }
686: childNode.setCopyFromPath(null);
687: childNode
688: .setCopyFromRevision(FSRepository.SVN_INVALID_REVNUM);
689: childNode.setPredecessorId(childNode.getId());
690: if (childNode.getCount() != -1) {
691: childNode.setCount(childNode.getCount() + 1);
692: }
693: childNode.setCreatedPath(SVNPathUtil.concatToAbs(
694: parentPath, childName));
695: newNodeId = createSuccessor(childNode.getId(), childNode,
696: copyId);
697: setEntry(parent, childName, newNodeId, childNode.getType());
698: }
699: return getOwner().getRevisionNode(newNodeId);
700: }
701:
702: private long writeHashRepresentation(Map hashContents,
703: OutputStream protoFile, MessageDigest digest)
704: throws IOException, SVNException {
705: HashRepresentationStream targetFile = new HashRepresentationStream(
706: protoFile, digest);
707: String header = FSRepresentation.REP_PLAIN + "\n";
708: protoFile.write(header.getBytes("UTF-8"));
709: SVNProperties.setProperties(hashContents, targetFile,
710: SVNProperties.SVN_HASH_TERMINATOR);
711: String trailer = FSRepresentation.REP_TRAILER + "\n";
712: protoFile.write(trailer.getBytes("UTF-8"));
713: return targetFile.mySize;
714: }
715:
716: public File getTransactionRevNodePropsFile(FSID id) {
717: return new File(getOwner().getTransactionDir(id.getTxnID()),
718: FSFS.PATH_PREFIX_NODE + id.getNodeID() + "."
719: + id.getCopyID() + FSFS.TXN_PATH_EXT_PROPS);
720: }
721:
722: public File getTransactionRevNodeChildrenFile(FSID id) {
723: return new File(getOwner().getTransactionDir(id.getTxnID()),
724: FSFS.PATH_PREFIX_NODE + id.getNodeID() + "."
725: + id.getCopyID() + FSFS.TXN_PATH_EXT_CHILDREN);
726: }
727:
728: public File getTransactionRevFile() {
729: if (myTxnRevFile == null) {
730: myTxnRevFile = new File(getOwner().getTransactionDir(
731: myTxnID), FSFS.TXN_PATH_REV);
732: }
733: return myTxnRevFile;
734: }
735:
736: public File getTransactionChangesFile() {
737: if (myTxnChangesFile == null) {
738: myTxnChangesFile = new File(getOwner().getTransactionDir(
739: myTxnID), "changes");
740: }
741: return myTxnChangesFile;
742: }
743:
744: public static String generateNextKey(String oldKey)
745: throws SVNException {
746: char[] nextKey = new char[oldKey.length() + 1];
747: boolean carry = true;
748: if (oldKey.length() > 1 && oldKey.charAt(0) == '0') {
749: return null;
750: }
751: for (int i = oldKey.length() - 1; i >= 0; i--) {
752: char c = oldKey.charAt(i);
753: if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z'))) {
754: return null;
755: }
756: if (carry) {
757: if (c == 'z') {
758: nextKey[i] = '0';
759: } else {
760: carry = false;
761: if (c == '9') {
762: nextKey[i] = 'a';
763: } else {
764: nextKey[i] = (char) (c + 1);
765: }
766: }
767: } else {
768: nextKey[i] = c;
769: }
770: }
771: int nextKeyLength = oldKey.length() + (carry ? 1 : 0);
772: if (nextKeyLength >= MAX_KEY_SIZE) {
773: SVNErrorMessage err = SVNErrorMessage
774: .create(
775: SVNErrorCode.UNKNOWN,
776: "FATAL error: new key length is greater than the threshold {0,number,integer}",
777: new Integer(MAX_KEY_SIZE));
778: SVNErrorManager.error(err);
779: }
780: if (carry) {
781: System.arraycopy(nextKey, 0, nextKey, 1, oldKey.length());
782: nextKey[0] = '1';
783: }
784: return new String(nextKey, 0, nextKeyLength);
785: }
786:
787: private static String addKeys(String key1, String key2) {
788: int i1 = key1.length() - 1;
789: int i2 = key2.length() - 1;
790: int carry = 0;
791: int val;
792: StringBuffer result = new StringBuffer();
793:
794: while (i1 >= 0 || i2 >= 0 || carry > 0) {
795: val = carry;
796:
797: if (i1 >= 0) {
798: val += key1.charAt(i1) <= '9' ? key1.charAt(i1) - '0'
799: : key1.charAt(i1) - 'a' + 10;
800: }
801:
802: if (i2 >= 0) {
803: val += key2.charAt(i2) <= '9' ? key2.charAt(i2) - '0'
804: : key2.charAt(i2) - 'a' + 10;
805: }
806:
807: carry = val / 36;
808: val = val % 36;
809:
810: char sym = val <= 9 ? (char) ('0' + val)
811: : (char) (val - 10 + 'a');
812: result.append(sym);
813:
814: if (i1 >= 0) {
815: --i1;
816: }
817:
818: if (i2 >= 0) {
819: --i2;
820: }
821: }
822:
823: return result.reverse().toString();
824: }
825:
826: private static class HashRepresentationStream extends OutputStream {
827:
828: long mySize;
829: MessageDigest myChecksum;
830: OutputStream myProtoFile;
831:
832: public HashRepresentationStream(OutputStream protoFile,
833: MessageDigest digest) {
834: super ();
835: mySize = 0;
836: myChecksum = digest;
837: myProtoFile = protoFile;
838: }
839:
840: public void write(int b) throws IOException {
841: myProtoFile.write(b);
842: if (myChecksum != null) {
843: myChecksum.update((byte) b);
844: }
845: mySize++;
846: }
847:
848: public void write(byte[] b, int off, int len)
849: throws IOException {
850: myProtoFile.write(b, off, len);
851: if (myChecksum != null) {
852: myChecksum.update(b, off, len);
853: }
854: mySize += len;
855: }
856:
857: public void write(byte[] b) throws IOException {
858: myProtoFile.write(b);
859: if (myChecksum != null) {
860: myChecksum.update(b);
861: }
862: mySize += b.length;
863: }
864: }
865: }
|