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.examples.repository;
013:
014: import java.io.ByteArrayInputStream;
015: import java.io.File;
016: import java.util.Iterator;
017: import java.util.Map;
018:
019: import org.tmatesoft.svn.core.SVNCancelException;
020: import org.tmatesoft.svn.core.SVNCommitInfo;
021: import org.tmatesoft.svn.core.SVNErrorCode;
022: import org.tmatesoft.svn.core.SVNException;
023: import org.tmatesoft.svn.core.SVNLogEntry;
024: import org.tmatesoft.svn.core.SVNURL;
025: import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
026: import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
027: import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
028: import org.tmatesoft.svn.core.io.ISVNEditor;
029: import org.tmatesoft.svn.core.io.SVNRepository;
030: import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
031: import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
032: import org.tmatesoft.svn.core.replicator.ISVNReplicationHandler;
033: import org.tmatesoft.svn.core.replicator.SVNRepositoryReplicator;
034: import org.tmatesoft.svn.core.wc.SVNWCUtil;
035:
036: /*
037: * This example shows how you can synchronize two repositories
038: * with the help of SVNKit. The program accepts
039: * two args: url of the source repository and the filesystem path
040: * of the target repository.
041: * If they're not provided the program uses default locations.
042: *
043: * If no args are provided both repositories are first created, then
044: * the source one is populated with some data up to revision 4. And
045: * lastly the source repository is replicated to the target one
046: * (all those 4 revisions), so that when the program exits you have two repositories with the
047: * same data.
048: *
049: * Below you can see a program layout:
050: *
051: r1 by 'me' at Wed Apr 26 02:10:14 NOVST 2006
052: r2 by 'me' at Wed Apr 26 02:10:14 NOVST 2006
053: r3 by 'me' at Wed Apr 26 02:10:15 NOVST 2006
054: r4 by 'me' at Wed Apr 26 02:10:15 NOVST 2006
055:
056: The latest source revision: 4
057:
058: 'file:///G:/tgtRepository' repository tree:
059:
060: /dirA (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006)
061: /dirA/dirB (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006)
062: /dirA/dirB/fileB.txt (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006)
063: /dirA/fileA.txt (author: 'me'; revision: 2; date: Wed Apr 26 02:10:14 NOVST 2006)
064:
065: Committed revision 1
066: Committed revision 2
067: Committed revision 3
068: Committed revision 4
069:
070: Number of replicated revisions: 4
071:
072: 'file:///G:/tgtRepository' repository tree:
073:
074: /dirA (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006)
075: /dirA/dirB (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006)
076: /dirA/dirB/fileB.txt (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006)
077: /dirA/fileA.txt (author: 'me'; revision: 2; date: Wed Apr 26 02:10:14 NOVST 2006)
078: */
079: public class Replicate {
080:
081: public static void main(String[] args) {
082: /*
083: * Default values:
084: * source and target repository paths
085: */
086: String srcPath = "srcRepository";
087: String tgtPath = "tgtRepository";
088: String srcUrl = null;
089: /*
090: * Initializes the library (it must be done before ever using the
091: * library itself)
092: */
093: setupLibrary();
094:
095: if (args != null) {
096: /*
097: * a source repository url
098: */
099: srcUrl = (args.length >= 1) ? args[0] : srcUrl;
100: /*
101: * a target repository path
102: */
103: tgtPath = (args.length >= 2) ? args[1] : tgtPath;
104: }
105:
106: SVNURL srcURL = null;
107: SVNURL tgtURL = null;
108: SVNRepository srcRepository = null;
109: SVNRepository tgtRepository = null;
110: boolean createSrcRepos = false;
111: boolean populateSrcRepos = false;
112: try {
113: if (srcUrl != null) {
114: /*
115: * If a source url was provided, using it as a source repository
116: */
117: srcURL = SVNURL.parseURIDecoded(srcUrl);
118: if ("file".equals(srcURL.getProtocol())) {
119: File srcReposDir = new File(srcURL.getPath());
120: if (!srcReposDir.exists()) {
121: /*
122: * it's a local access scheme and src path does not exist -
123: * we'll need to create something
124: */
125: createSrcRepos = true;
126: populateSrcRepos = true;
127: srcPath = srcURL.getPath();
128: } else {
129: srcRepository = SVNRepositoryFactory
130: .create(srcURL);
131: if (srcRepository.getLatestRevision() == 0) {
132: /*
133: * it's a local access scheme, src path already
134: * exists, but seems to be an empty repository -
135: * we'll need to create something in it
136: */
137: populateSrcRepos = true;
138: }
139: }
140: }
141: } else {
142: createSrcRepos = true;
143: populateSrcRepos = true;
144: }
145:
146: if (createSrcRepos) {
147: /*
148: * Creat a source repository with
149: * SVNRepositoryFactory.createLocalRepository(File path,
150: * boolean enableRevisionProperties, boolean force):
151: *
152: * The second (boolean) parameter controls whether a new
153: * repository is created with an ability for changes to
154: * revision properties enabled or not. By default
155: * Subversion repositories disallow such changes until you
156: * put a pre-revprop-change hook that allows them to the
157: * repository. So, if the second parameter is true SVNKit
158: * creates a repository with an empty pre-revprop-change hook,
159: * so that you don't have to care of that by yourself. In
160: * this program we're not going to modify revision props of
161: * the source repository, so we pass this param set to false.
162: *
163: * The third (boolean) and the last parameter forces a
164: * repository creation. If it's false like in our case
165: * a new repository won't be created if the specified path already
166: * exists, the method throws an SVNException.
167: * However if this param is true, the existing path will
168: * be deleted and a repository will be created in the same location.
169: *
170: * After the repository is successfully created, the createLocalRepository()
171: * method returns an SVNURL representing a 'file:///' url to the repository which
172: * can be further used to create an SVNRepository driver.
173: */
174: srcURL = SVNRepositoryFactory.createLocalRepository(
175: new File(srcPath), false, false);
176: }
177:
178: /*
179: * For the target repository we need to enable revision property
180: * changes.
181: */
182: tgtURL = SVNRepositoryFactory.createLocalRepository(
183: new File(tgtPath), true, false);
184:
185: srcRepository = SVNRepositoryFactory.create(srcURL);
186: tgtRepository = SVNRepositoryFactory.create(tgtURL);
187: } catch (SVNException svne) {
188: /*
189: * getFullMessage() will give us a full tree of SVNException
190: * errors.
191: */
192: System.err.println("Can not create a repository: "
193: + svne.getErrorMessage().getFullMessage());
194: System.exit(1);
195: }
196:
197: /*
198: * Deault auth manager is used to cache a username in the
199: * default Subversion credentials storage area.
200: */
201: srcRepository.setAuthenticationManager(SVNWCUtil
202: .createDefaultAuthenticationManager());
203: tgtRepository.setAuthenticationManager(SVNWCUtil
204: .createDefaultAuthenticationManager());
205:
206: try {
207: if (populateSrcRepos) {
208: /*
209: * Fills up the source repository with some data.
210: */
211: populateSourceRepository(srcRepository);
212: }
213: System.out.println();
214: long srcLatestRevision = srcRepository.getLatestRevision();
215: System.out.println("The latest source revision: "
216: + srcLatestRevision);
217: System.out.println();
218: System.out.println("'" + srcURL + "' repository tree:");
219: System.out.println();
220: /*
221: * Using the DisplayRepositoryTree example to show the source
222: * repository tree in the latest revision.
223: */
224: DisplayRepositoryTree.listEntries(srcRepository, "");
225: System.out.println();
226: } catch (SVNException svne) {
227: System.err
228: .println("An error occurred while accessing source repository: "
229: + svne.getErrorMessage().getFullMessage());
230: System.exit(1);
231: }
232:
233: try {
234: /*
235: * First let's try the standard replay way.
236: */
237: long replicatedRevisions = 0;
238: try {
239: initializeRepository(srcRepository, tgtRepository);
240: replicatedRevisions = synchronizeRepository(
241: srcRepository, tgtRepository);
242: } catch (SVNException svne) {
243: if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.RA_NOT_IMPLEMENTED) {
244: throw svne;
245: }
246:
247: /* ...else the server does not support replay functionality, we try our own
248: * replication engine. It could have happened if we used http:// or
249: * svn:// access protocols, and the server had binaries younger than the
250: * Subversion 1.4.0
251: */
252:
253: /*
254: * Create a new SVNRepository for the target url, since the one we
255: * used may be locked by a commit editor it provides for committing, in
256: * this case we'll get an SVNException saying that '..methods of SVNRepository
257: * are not reenterable'.
258: */
259: tgtRepository = SVNRepositoryFactory.create(tgtURL);
260: replicatedRevisions = replicateRepository(
261: srcRepository, tgtRepository);
262: }
263:
264: System.out.println();
265: System.out.println("Number of replicated revisions: "
266: + replicatedRevisions);
267: System.out.println();
268: System.out.println("'" + tgtURL + "' repository tree:");
269: System.out.println();
270: /*
271: * Shows the tree of the target repository in the latest revision.
272: */
273: DisplayRepositoryTree.listEntries(tgtRepository, "");
274: } catch (SVNException svne) {
275: System.err
276: .println("An error occurred while accessing source repository: "
277: + svne.getErrorMessage().getFullMessage());
278: System.exit(1);
279: }
280:
281: }
282:
283: /*
284: * This is a very simplified form of SVNAdminClient.doInitialize()
285: * that we use here only for replay functionality demonstration.
286: */
287: private static void initializeRepository(SVNRepository fromRepos,
288: SVNRepository toRepos) throws SVNException {
289: /*
290: * Initialization means we need to set necessary svn:sync- properties
291: * on revision 0 of the destination repository. But since our program
292: * is just an example, we copy only revision properties from the
293: * source repository to the destination one.
294: */
295: copyRevisionProperties(fromRepos, toRepos, 0);
296: }
297:
298: /*
299: * This method does not make all necessary checks that would allow
300: * us to copy revision properties correctly under any conditions...
301: * But it's suitable enough for our example.
302: */
303: private static void copyRevisionProperties(
304: SVNRepository fromRepository, SVNRepository toRepository,
305: long revision) throws SVNException {
306: Map revProps = fromRepository.getRevisionProperties(revision,
307: null);
308: for (Iterator propNames = revProps.keySet().iterator(); propNames
309: .hasNext();) {
310: String propName = (String) propNames.next();
311: String propValue = (String) revProps.get(propName);
312: toRepository.setRevisionPropertyValue(revision, propName,
313: propValue);
314: }
315: }
316:
317: /*
318: * This is a very simplified form of SVNAdminClient.doSynchronize()
319: * that we use here only for replay functionality demonstration.
320: */
321: private static long synchronizeRepository(SVNRepository fromRepos,
322: SVNRepository toRepos) throws SVNException {
323: long lastMergedRevision = 0;
324: long fromLatestRevision = fromRepos.getLatestRevision();
325: long count = 0;
326: for (long currentRev = lastMergedRevision + 1; currentRev <= fromLatestRevision; currentRev++) {
327: ISVNEditor commitEditor = toRepos.getCommitEditor("", null);
328: fromRepos.replay(0, currentRev, true, commitEditor);
329: SVNCommitInfo info = commitEditor.closeEdit();
330:
331: if (info.getNewRevision() != currentRev) {
332: System.err.println("Commit created rev "
333: + info.getNewRevision()
334: + " but should have created " + currentRev);
335: System.exit(1);
336: }
337: System.out.println("Committed revision "
338: + info.getNewRevision());
339: copyRevisionProperties(fromRepos, toRepos, currentRev);
340: count++;
341: }
342: return count;
343: }
344:
345: private static void populateSourceRepository(
346: SVNRepository srcRepository) throws SVNException {
347: /*
348: * Simple repository tree to create. Each entry will be
349: * added in its own revision.
350: */
351: String dirA = "dirA";
352: String dirB = "dirA/dirB";
353: String fileA = "dirA/fileA.txt";
354: String fileB = "dirA/dirB/fileB.txt";
355: byte[] fileAContents = "This is file fileA.txt".getBytes();
356: byte[] fileBContents = "This is file fileB.txt".getBytes();
357: SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
358: long revision = -1;
359: SVNCommitInfo info = null;
360: String checksum = null;
361:
362: /*
363: * First commit "/dirA".
364: */
365: ISVNEditor editor = srcRepository.getCommitEditor("adding "
366: + dirA, null);
367: editor.openRoot(-1);
368: editor.addDir(dirA, null, -1);
369: editor.closeDir();
370: editor.closeDir();
371: info = editor.closeEdit();
372: System.out.println(info);
373: revision = info.getNewRevision();
374:
375: /*
376: * Then commit "/dirA/fileA.txt".
377: */
378: editor = srcRepository.getCommitEditor("adding " + fileA, null);
379: editor.openRoot(-1);
380: editor.openDir(dirA, revision);
381: editor.addFile(fileA, null, -1);
382: editor.applyTextDelta(fileA, null);
383: checksum = deltaGenerator.sendDelta(fileA,
384: new ByteArrayInputStream(fileAContents), editor, true);
385: editor.closeFile(fileA, checksum);
386: editor.closeDir();
387: editor.closeDir();
388: info = editor.closeEdit();
389: System.out.println(info);
390: revision = info.getNewRevision();
391:
392: /*
393: * Then commit "/dirA/dirB".
394: */
395: editor = srcRepository.getCommitEditor("adding " + dirB, null);
396: editor.openRoot(-1);
397: editor.openDir(dirA, revision);
398: editor.addDir(dirB, null, -1);
399: editor.closeDir();
400: editor.closeDir();
401: editor.closeDir();
402: info = editor.closeEdit();
403: System.out.println(info);
404: revision = info.getNewRevision();
405:
406: /*
407: * Then commit "/dirA/dirB/fileB.txt".
408: */
409: editor = srcRepository.getCommitEditor("adding " + fileB, null);
410: editor.openRoot(-1);
411: editor.openDir(dirA, revision);
412: editor.openDir(dirB, revision);
413: editor.addFile(fileB, null, -1);
414: editor.applyTextDelta(fileB, null);
415: checksum = deltaGenerator.sendDelta(fileB,
416: new ByteArrayInputStream(fileBContents), editor, true);
417: editor.closeFile(fileB, checksum);
418: editor.closeDir();
419: editor.closeDir();
420: editor.closeDir();
421: info = editor.closeEdit();
422: System.out.println(info);
423: }
424:
425: /*
426: * This routine makes synchronization between two repositories
427: * using SVNKit's own feature - replicator.
428: */
429: private static long replicateRepository(
430: SVNRepository srcRepository, SVNRepository tgtRepository)
431: throws SVNException {
432: long latestRevision = srcRepository.getLatestRevision();
433: SVNRepositoryReplicator replicator = SVNRepositoryReplicator
434: .newInstance();
435: replicator.setReplicationHandler(new ISVNReplicationHandler() {
436:
437: public void revisionReplicated(
438: SVNRepositoryReplicator source,
439: SVNCommitInfo commitInfo) throws SVNException {
440: System.out.println("Committed revision "
441: + commitInfo.getNewRevision());
442: }
443:
444: public void revisionReplicating(
445: SVNRepositoryReplicator source, SVNLogEntry logEntry)
446: throws SVNException {
447: }
448:
449: public void checkCancelled() throws SVNCancelException {
450: }
451: });
452:
453: /*
454: * Replicates the source repository into the target one starting with the
455: * first revision and up to the latest revision of the source repository.
456: * The same result is reached if you call:
457: *
458: * replicator.replicateRepository(srcRepository, tgtRepository, -1, -1)
459: *
460: * The return value is the total number of replicated revisions.
461: */
462: return replicator.replicateRepository(srcRepository,
463: tgtRepository, 1, latestRevision);
464: }
465:
466: /*
467: * Initializes the library to work with a repository via
468: * different protocols.
469: */
470: private static void setupLibrary() {
471: /*
472: * For using over http:// and https://
473: */
474: DAVRepositoryFactory.setup();
475: /*
476: * For using over svn:// and svn+xxx://
477: */
478: SVNRepositoryFactoryImpl.setup();
479: /*
480: * For using over file:///
481: */
482: FSRepositoryFactory.setup();
483: }
484:
485: }
|