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.wc;
013:
014: import java.io.File;
015: import java.io.IOException;
016: import java.io.InputStream;
017: import java.io.OutputStream;
018: import java.util.HashMap;
019: import java.util.Iterator;
020: import java.util.LinkedList;
021: import java.util.Map;
022:
023: import org.tmatesoft.svn.core.SVNErrorCode;
024: import org.tmatesoft.svn.core.SVNErrorMessage;
025: import org.tmatesoft.svn.core.SVNException;
026: import org.tmatesoft.svn.core.SVNNodeKind;
027: import org.tmatesoft.svn.core.SVNRevisionProperty;
028: import org.tmatesoft.svn.core.internal.io.fs.FSEntry;
029: import org.tmatesoft.svn.core.internal.io.fs.FSFS;
030: import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryUtil;
031: import org.tmatesoft.svn.core.internal.io.fs.FSRevisionNode;
032: import org.tmatesoft.svn.core.internal.io.fs.FSRevisionRoot;
033: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
034: import org.tmatesoft.svn.core.io.ISVNEditor;
035: import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
036: import org.tmatesoft.svn.core.wc.SVNRevision;
037:
038: /**
039: * @version 1.1.1
040: * @author TMate Software Ltd.
041: */
042: public class SVNAdminHelper {
043:
044: public static FSFS openRepository(File reposRootPath)
045: throws SVNException {
046: FSFS fsfs = new FSFS(reposRootPath);
047: fsfs.open();
048: return fsfs;
049: }
050:
051: public static long getRevisionNumber(SVNRevision revision,
052: long youngestRevision, FSFS fsfs) throws SVNException {
053: long revNumber = -1;
054: if (revision.getNumber() >= 0) {
055: revNumber = revision.getNumber();
056: } else if (revision == SVNRevision.HEAD) {
057: revNumber = youngestRevision;
058: } else if (revision.getDate() != null) {
059: revNumber = fsfs.getDatedRevision(revision.getDate());
060: } else if (revision != SVNRevision.UNDEFINED) {
061: SVNErrorMessage err = SVNErrorMessage.create(
062: SVNErrorCode.CL_ARG_PARSING_ERROR,
063: "Invalid revision specifier");
064: SVNErrorManager.error(err);
065: }
066:
067: if (revNumber > youngestRevision) {
068: SVNErrorMessage err = SVNErrorMessage
069: .create(
070: SVNErrorCode.CL_ARG_PARSING_ERROR,
071: "Revisions must not be greater than the youngest revision ({0,number,integer})",
072: new Long(youngestRevision));
073: SVNErrorManager.error(err);
074: }
075: return revNumber;
076: }
077:
078: public static void writeProperties(Map props, Map oldProps,
079: OutputStream dumpStream) throws SVNException {
080: LinkedList propNames = new LinkedList();
081: for (Iterator names = props.keySet().iterator(); names
082: .hasNext();) {
083: String propName = (String) names.next();
084: if (SVNRevisionProperty.LOG.equals(propName)) {
085: propNames.addFirst(propName);
086: } else if (SVNRevisionProperty.AUTHOR.equals(propName)) {
087: if (propNames.contains(SVNRevisionProperty.LOG)) {
088: int ind = propNames
089: .indexOf(SVNRevisionProperty.LOG);
090: propNames.add(ind + 1, propName);
091: } else {
092: propNames.addFirst(propName);
093: }
094: } else {
095: propNames.addLast(propName);
096: }
097: }
098:
099: for (Iterator names = propNames.iterator(); names.hasNext();) {
100: String propName = (String) names.next();
101: String propValue = (String) props.get(propName);
102: if (oldProps != null) {
103: String oldValue = (String) oldProps.get(propName);
104: if (oldValue != null && oldValue.equals(propValue)) {
105: continue;
106: }
107: }
108:
109: SVNProperties.appendProperty(propName, propValue,
110: dumpStream);
111: }
112:
113: if (oldProps != null) {
114: for (Iterator names = oldProps.keySet().iterator(); names
115: .hasNext();) {
116: String propName = (String) names.next();
117: if (props.containsKey(propName)) {
118: continue;
119: }
120: SVNProperties.appendPropertyDeleted(propName,
121: dumpStream);
122:
123: }
124: }
125:
126: try {
127: byte[] terminator = "PROPS-END\n".getBytes("UTF-8");
128: dumpStream.write(terminator);
129: } catch (IOException ioe) {
130: SVNErrorMessage err = SVNErrorMessage.create(
131: SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
132: SVNErrorManager.error(err, ioe);
133: }
134: }
135:
136: public static void deltifyDir(FSFS fsfs, FSRevisionRoot srcRoot,
137: String srcParentDir, String srcEntry,
138: FSRevisionRoot tgtRoot, String tgtFullPath,
139: ISVNEditor editor) throws SVNException {
140: if (srcParentDir == null) {
141: generateNotADirError("source parent", srcParentDir);
142: }
143:
144: if (tgtFullPath == null) {
145: SVNErrorMessage err = SVNErrorMessage.create(
146: SVNErrorCode.FS_PATH_SYNTAX, "Invalid target path");
147: SVNErrorManager.error(err);
148: }
149:
150: String srcFullPath = SVNPathUtil.concatToAbs(srcParentDir,
151: srcEntry);
152: SVNNodeKind tgtKind = tgtRoot.checkNodeKind(tgtFullPath);
153: SVNNodeKind srcKind = srcRoot.checkNodeKind(srcFullPath);
154:
155: if (tgtKind == SVNNodeKind.NONE && srcKind == SVNNodeKind.NONE) {
156: editor.closeEdit();
157: return;
158: }
159:
160: if (srcEntry == null
161: && (srcKind != SVNNodeKind.DIR || tgtKind != SVNNodeKind.DIR)) {
162: SVNErrorMessage err = SVNErrorMessage
163: .create(
164: SVNErrorCode.FS_PATH_SYNTAX,
165: "Invalid editor anchoring; at least one of the input paths is not a directory and there was no source entry");
166: SVNErrorManager.error(err);
167: }
168:
169: editor.targetRevision(tgtRoot.getRevision());
170: long rootRevision = srcRoot.getRevision();
171: if (tgtKind == SVNNodeKind.NONE) {
172: editor.openRoot(rootRevision);
173: editor.deleteEntry(srcEntry, -1);
174: editor.closeDir();
175: editor.closeEdit();
176: return;
177: }
178: if (srcKind == SVNNodeKind.NONE) {
179: editor.openRoot(rootRevision);
180: addFileOrDir(fsfs, editor, srcRoot, tgtRoot, tgtFullPath,
181: srcEntry, tgtKind);
182: editor.closeDir();
183: editor.closeEdit();
184: return;
185: }
186:
187: FSRevisionNode srcNode = srcRoot.getRevisionNode(srcFullPath);
188: FSRevisionNode tgtNode = tgtRoot.getRevisionNode(tgtFullPath);
189: int distance = srcNode.getId().compareTo(tgtNode.getId());
190: if (distance == 0) {
191: editor.closeEdit();
192: return;
193: } else if (srcEntry != null) {
194: if (srcKind != tgtKind || distance == -1) {
195: editor.openRoot(rootRevision);
196: editor.deleteEntry(srcEntry, -1);
197: addFileOrDir(fsfs, editor, srcRoot, tgtRoot,
198: tgtFullPath, srcEntry, tgtKind);
199: } else {
200: editor.openRoot(rootRevision);
201: replaceFileOrDir(fsfs, editor, srcRoot, tgtRoot,
202: srcFullPath, tgtFullPath, srcEntry, tgtKind);
203: }
204: editor.closeDir();
205: editor.closeEdit();
206: } else {
207: editor.openRoot(rootRevision);
208: deltifyDirs(fsfs, editor, srcRoot, tgtRoot, srcFullPath,
209: tgtFullPath, "");
210: editor.closeDir();
211: editor.closeEdit();
212: }
213: }
214:
215: public static void generateIncompleteDataError()
216: throws SVNException {
217: SVNErrorMessage err = SVNErrorMessage.create(
218: SVNErrorCode.INCOMPLETE_DATA,
219: "Premature end of content data in dumpstream");
220: SVNErrorManager.error(err);
221: }
222:
223: public static void generateStreamMalformedError()
224: throws SVNException {
225: SVNErrorMessage err = SVNErrorMessage.create(
226: SVNErrorCode.STREAM_MALFORMED_DATA,
227: "Dumpstream data appears to be malformed");
228: SVNErrorManager.error(err);
229: }
230:
231: public static int readKeyOrValue(InputStream dumpStream,
232: byte[] buffer, int len) throws SVNException, IOException {
233: int r = dumpStream.read(buffer);
234:
235: if (r != len) {
236: SVNAdminHelper.generateIncompleteDataError();
237: }
238:
239: int readLength = r;
240:
241: r = dumpStream.read();
242: if (r == -1) {
243: SVNAdminHelper.generateIncompleteDataError();
244: } else if (r != '\n') {
245: SVNAdminHelper.generateStreamMalformedError();
246: }
247:
248: return ++readLength;
249: }
250:
251: private static void addFileOrDir(FSFS fsfs, ISVNEditor editor,
252: FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot,
253: String tgtPath, String editPath, SVNNodeKind tgtKind)
254: throws SVNException {
255: if (tgtKind == SVNNodeKind.DIR) {
256: editor.addDir(editPath, null, -1);
257: deltifyDirs(fsfs, editor, srcRoot, tgtRoot, null, tgtPath,
258: editPath);
259: editor.closeDir();
260: } else {
261: editor.addFile(editPath, null, -1);
262: deltifyFiles(fsfs, editor, srcRoot, tgtRoot, null, tgtPath,
263: editPath);
264: FSRevisionNode tgtNode = tgtRoot.getRevisionNode(tgtPath);
265: editor.closeFile(editPath, tgtNode.getFileChecksum());
266: }
267: }
268:
269: private static void replaceFileOrDir(FSFS fsfs, ISVNEditor editor,
270: FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot,
271: String srcPath, String tgtPath, String editPath,
272: SVNNodeKind tgtKind) throws SVNException {
273: long baseRevision = srcRoot.getRevision();
274: if (tgtKind == SVNNodeKind.DIR) {
275: editor.openDir(editPath, baseRevision);
276: deltifyDirs(fsfs, editor, srcRoot, tgtRoot, srcPath,
277: tgtPath, editPath);
278: editor.closeDir();
279: } else {
280: editor.openFile(editPath, baseRevision);
281: deltifyFiles(fsfs, editor, srcRoot, tgtRoot, srcPath,
282: tgtPath, editPath);
283: FSRevisionNode tgtNode = tgtRoot.getRevisionNode(tgtPath);
284: editor.closeFile(editPath, tgtNode.getFileChecksum());
285: }
286: }
287:
288: private static void deltifyFiles(FSFS fsfs, ISVNEditor editor,
289: FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot,
290: String srcPath, String tgtPath, String editPath)
291: throws SVNException {
292: deltifyProperties(fsfs, editor, srcRoot, tgtRoot, srcPath,
293: tgtPath, editPath, false);
294:
295: boolean changed = false;
296: if (srcPath != null) {
297: changed = FSRepositoryUtil.areFileContentsChanged(srcRoot,
298: srcPath, tgtRoot, tgtPath);
299: }
300:
301: if (changed) {
302: String srcHexDigest = null;
303: if (srcPath != null) {
304: FSRevisionNode srcNode = srcRoot
305: .getRevisionNode(srcPath);
306: srcHexDigest = srcNode.getFileChecksum();
307: }
308: editor.applyTextDelta(editPath, srcHexDigest);
309: editor.textDeltaChunk(editPath, SVNDiffWindow.EMPTY);
310: }
311: }
312:
313: private static void deltifyDirs(FSFS fsfs, ISVNEditor editor,
314: FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot,
315: String srcPath, String tgtPath, String editPath)
316: throws SVNException {
317: deltifyProperties(fsfs, editor, srcRoot, tgtRoot, srcPath,
318: tgtPath, editPath, true);
319:
320: FSRevisionNode targetNode = tgtRoot.getRevisionNode(tgtPath);
321: Map targetEntries = targetNode.getDirEntries(fsfs);
322:
323: Map sourceEntries = null;
324: if (srcPath != null) {
325: FSRevisionNode sourceNode = srcRoot
326: .getRevisionNode(srcPath);
327: sourceEntries = sourceNode.getDirEntries(fsfs);
328: }
329:
330: for (Iterator tgtEntries = targetEntries.keySet().iterator(); tgtEntries
331: .hasNext();) {
332: String name = (String) tgtEntries.next();
333: FSEntry tgtEntry = (FSEntry) targetEntries.get(name);
334:
335: SVNNodeKind tgtKind = tgtEntry.getType();
336: String targetFullPath = SVNPathUtil.concatToAbs(tgtPath,
337: tgtEntry.getName());
338: String editFullPath = SVNPathUtil.concatToAbs(editPath,
339: tgtEntry.getName());
340:
341: if (sourceEntries != null
342: && sourceEntries.containsKey(name)) {
343: FSEntry srcEntry = (FSEntry) sourceEntries.get(name);
344: String sourceFullPath = SVNPathUtil.concatToAbs(
345: srcPath, tgtEntry.getName());
346: SVNNodeKind srcKind = srcEntry.getType();
347:
348: int distance = srcEntry.getId().compareTo(
349: tgtEntry.getId());
350: if (srcKind != tgtKind || distance == -1) {
351: editor.deleteEntry(editFullPath, -1);
352: addFileOrDir(fsfs, editor, srcRoot, tgtRoot,
353: targetFullPath, editFullPath, tgtKind);
354: } else if (distance != 0) {
355: replaceFileOrDir(fsfs, editor, srcRoot, tgtRoot,
356: sourceFullPath, targetFullPath,
357: editFullPath, tgtKind);
358: }
359: sourceEntries.remove(name);
360: } else {
361: addFileOrDir(fsfs, editor, srcRoot, tgtRoot,
362: targetFullPath, editFullPath, tgtKind);
363: }
364: }
365:
366: if (sourceEntries != null) {
367: for (Iterator srcEntries = sourceEntries.keySet()
368: .iterator(); srcEntries.hasNext();) {
369: String name = (String) srcEntries.next();
370: FSEntry entry = (FSEntry) sourceEntries.get(name);
371: String editFullPath = SVNPathUtil.concatToAbs(editPath,
372: entry.getName());
373: editor.deleteEntry(editFullPath, -1);
374: }
375: }
376: }
377:
378: private static void deltifyProperties(FSFS fsfs, ISVNEditor editor,
379: FSRevisionRoot srcRoot, FSRevisionRoot tgtRoot,
380: String srcPath, String tgtPath, String editPath,
381: boolean isDir) throws SVNException {
382: FSRevisionNode targetNode = tgtRoot.getRevisionNode(tgtPath);
383:
384: Map sourceProps = null;
385: if (srcPath != null) {
386: FSRevisionNode sourceNode = srcRoot
387: .getRevisionNode(srcPath);
388: boolean propsChanged = !FSRepositoryUtil
389: .arePropertiesEqual(sourceNode, targetNode);
390: if (!propsChanged) {
391: return;
392: }
393: sourceProps = sourceNode.getProperties(fsfs);
394: } else {
395: sourceProps = new HashMap();
396: }
397:
398: Map targetProps = targetNode.getProperties(fsfs);
399: Map propsDiffs = FSRepositoryUtil.getPropsDiffs(sourceProps,
400: targetProps);
401: Object[] names = propsDiffs.keySet().toArray();
402: for (int i = 0; i < names.length; i++) {
403: String propName = (String) names[i];
404: String propValue = (String) propsDiffs.get(propName);
405: if (isDir) {
406: editor.changeDirProperty(propName, propValue);
407: } else {
408: editor
409: .changeFileProperty(editPath, propName,
410: propValue);
411: }
412: }
413: }
414:
415: private static void generateNotADirError(String role, String path)
416: throws SVNException {
417: SVNErrorMessage err = SVNErrorMessage.create(
418: SVNErrorCode.FS_NOT_DIRECTORY,
419: "Invalid {0} directory ''{1}''", new Object[] { role,
420: path != null ? path : "(null)" });
421: SVNErrorManager.error(err);
422: }
423:
424: public static final String DUMPFILE_MAGIC_HEADER = "SVN-fs-dump-format-version";
425: public static final String DUMPFILE_CONTENT_LENGTH = "Content-length";
426: public static final String DUMPFILE_NODE_ACTION = "Node-action";
427: public static final String DUMPFILE_NODE_COPYFROM_PATH = "Node-copyfrom-path";
428: public static final String DUMPFILE_NODE_COPYFROM_REVISION = "Node-copyfrom-rev";
429: public static final String DUMPFILE_NODE_KIND = "Node-kind";
430: public static final String DUMPFILE_NODE_PATH = "Node-path";
431: public static final String DUMPFILE_PROP_CONTENT_LENGTH = "Prop-content-length";
432: public static final String DUMPFILE_PROP_DELTA = "Prop-delta";
433: public static final String DUMPFILE_REVISION_NUMBER = "Revision-number";
434: public static final String DUMPFILE_TEXT_CONTENT_LENGTH = "Text-content-length";
435: public static final String DUMPFILE_TEXT_DELTA = "Text-delta";
436: public static final String DUMPFILE_UUID = "UUID";
437: public static final String DUMPFILE_TEXT_CONTENT_CHECKSUM = "Text-content-md5";
438: public static final int STREAM_CHUNK_SIZE = 16384;
439: public static final int DUMPFILE_FORMAT_VERSION = 3;
440:
441: public static final int NODE_ACTION_ADD = 1;
442: public static final int NODE_ACTION_CHANGE = 0;
443: public static final int NODE_ACTION_DELETE = 2;
444: public static final int NODE_ACTION_REPLACE = 3;
445: public static final int NODE_ACTION_UNKNOWN = -1;
446:
447: }
|