[[Main]] .+ [[What_Is_Subversion_For|What Is Subversion For]] .+ [[Setting_Up_A_Subversion_Repository|Setting Up A Subversion Repository]] .+ [[SVNKit_Architecture|SVNKit Architecture]] .+ [[Getting_Started_With_SVNKit|Getting Started With SVNKit]] .+ [[Authentication]] .+ [[Managing_Repository_With_SVNKit|Managing Repository With SVNKit]] .+ [[Printing_Out_A_Subversion_Repository_Tree|Printing Out A Subversion Repository Tree]] .+ [[Printing_Out_File_Contents|Printing Out File Contents]] .+ [[Printing_Out_Repository_History|Printing Out Repository History]] .+ [[Committing_To_A_Repository|Editing operation: committing to a repository]] .+ [[Updating_From_A_Repository|Editing Operation: receiving changes from a repository]] .- Replicating An Existing Repository .+ [[Managing_A_Working_Copy|Managing A Working Copy]] ---- <> = Replicating an existing repository = On the one hand [[SVNKit]] ver. 1.1 supports the Subversion ver. 1.4 feature of synchronizing two repositories. On a low-level the [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html|SVNRepository]] class provides a user [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html#replay(long,%20long,%20boolean,%20org.tmatesoft.svn.core.io.ISVNEditor)| replay()]] method which accepts a client's commit editor to commit all changes made in a particular revision of the source repository to a target one: {{{#!java public abstract void replay( long lowRevision , long revision , boolean sendDeltas , ISVNEditor editor ) throws SVNException; }}} [[SVNKit]] ver. 1.1 also brings a high-level API to do synchronization just as the Subversion's '''svnsync''' utility does. [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/wc/SVNAdminClient.java|SVNAdminClient]] provides such API methods as * [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/wc/SVNAdminClient.html#doInitialize(org.tmatesoft.svn.core.SVNURL,%20org.tmatesoft.svn.core.SVNURL)| doInitialize()]], similar to {{{svnsync initialize}}} command * [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/wc/SVNAdminClient.html#doSynchronize(org.tmatesoft.svn.core.SVNURL)|doSynchronize()]], similar to {{{svnsync synchronize}}} command * [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/wc/SVNAdminClient.html#doCopyRevisionProperties(org.tmatesoft.svn.core.SVNURL,%20long)|doCopyRevisionProperties()]], similar to {{{svnsync copy-revprops}}} command For repositories accessible through the {{{file:///}}} protocol or running under servers which support the '''replay''' functionality, this way of synchronizing two repositories should work well. But what if we would like to replicate a repository that is running under [[http://apache.org|Apache]] or [[http://svnbook.red-bean.com/nightly/en/svn.serverconfig.overview.html#svn.serverconfig.overview.svnserve|svnserve]] which don't support this functionality. In this case [[SVNKit]] provides its own feature that can replicate repositories running under older servers - repository replicator. The [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/replicator/package-summary.html|repository replicator]] is a tool that gives you an ability to create clones of existing repositories. That is, the replicator copies revisions of existing repositories to other clean repositories what results in two separate repositories containing the same versioned data. The replicator is represented by the class [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/replicator/SVNRepositoryReplicator.html|SVNRepositoryReplicator]]. Here's a lite class diagram demonstrating relationships between the replicator and other components which are involved by the replicator: {{attachment:Replicator_Diagram.png}} == Repository replicator: replicating a range of source repository revisions == There are two ways you can replicate repositories. The first one corresponds to using the replicator's {{{#!java public long replicateRepository( SVNRepository src , SVNRepository dst , long fromRevision , long toRevision ) throws SVNException }}} method. Both source ({{{src}}}) and destination ({{{dst}}}) [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html|SVNRepository]] drivers must be created for the root locations of the source and destination repositories respectively. That is, only entire repository trees can be replicated, but not sub-trees. A destination repository must be either completely clean (be at revision 0) or must already contain all source repository revisions in the range starting from revision 1 and up to {{{fromRevision - 1}}} inclusively. In all cases, if the destination repository latest revision is not equal to {{{fromRevision - 1}}}, you will get an exception. If {{{fromRevision}}} is less or equal to 0, it automatically defaults to revision 1, since revision 0 is always a point of repository life start and doesn't contain any user valuable data. In all cases when {{{toRevision}}} is not in this range - {{{toRevision > 0}}} and {{{toRevision < }}} source repository latest revision - it automatically defaults to the source repository latest revision. == Repository replicator: Replicating a source repository incrementally == This way of replicator usage corresponds to using the replicator's {{{#!java public long replicateRepository( SVNRepository src , SVNRepository dst , boolean incremental ) throws SVNException }}} method. ''Incrementally'' means that sometime you replicated the source repository into the destination one, and since then have committed some more revisions into the source repository. Or maybe that time you replicated not the entire range of the source repository revisions. And in case you would also like to copy those revisions to the destination repository, you perform an incremental copy - copy those missing revisions. In fact, this method is equivalent to {{{#!java long fromRevision = incremental ? dst.getLatestRevision( ) + 1 : 1; return replicateRepository( src , dst , fromRevision , -1 ); }}} That is, if you set {{{incremental}}} to {{{false}}} the whole source repository is replicated. Otherwise the source repository is incrementally copied up to the latest revision. == Repository replicator: replicating principles == Starting with the first revision to copy the replicator obtains information about all changed paths in the particular revision of the source repository. This information is analysed for the presence of paths copied in that revision. Then the replicator obtains a [:Committing To A Repository:commit editor] for the destination repository. This editor is passed to an instance of [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/replicator/SVNReplicationEditor.html|SVNReplicationEditor]]. A replication editor is like a bridge between the source and destination repositories: the replicator calls an update operation for the same particular revision on the source driver passing it a replication editor as an [[Updating_From_A_Repository|update editor]]. When the source driver calls the replication editor, the latter transforms these calls to calls to the commit editor of the destination driver. Thus changes received are ''redirected'' immediately to the destination repository. [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/replicator/ISVNReplicationHandler.html|SVNReplicationHandler]] is introduced to control replication process. At the start of each replication iteration the replicator checks the registered handler's method {{{#!java public void checkCancelled( ) throws SVNCancelException }}} To stop the replication process it's enough for the handler to throw an [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/SVNCancelException.html|SVNCancelException]] exception. {{{replicateRepository(...)}}} method will throw it upper. If replication is not cancelled and a log operation is successfully perfromed for a particular revision of the source repository, the replicator passes this log info to the registered handler's method {{{#!java public void revisionReplicating( SVNRepositoryReplicator source , SVNLogEntry logEntry ) throws SVNException }}} And after the current revision is successfully committed to the destination repository the replicaor passes commit info to the handler's method: {{{#!java public void revisionReplicated( SVNRepositoryReplicator source , SVNCommitInfo commitInfo ) throws SVNException; }}} The replicator copies both versioned and unversioned (revision properties) data. In theory, with the repository replicator you are able to create exact copies of repository contents, but in practice it strongly depends on permissions you have on both source and destination repositories. Say, if you are not able to read some directory in the source repository, it is obvious that this directory will be missing in the destination one. You can use different repository access [:SVNKit Architecture:protocols supported by SVNKit] for both source and destination repositories. This provides you an ability to make a local back-up of a repository located on the server machine. = Example: synchronizing/replicating a repository = In this example we will replicate a source repository to a local destination one. Our program should be able to receive two args: first one is the url of the source repository and the second one is the path of the target repository. If no args are provided, we create a local FSFS-type repository and fill it with some data. Here's a function which populates a repository with some dummy data. {{{#!java private static void populateSourceRepository( SVNRepository srcRepository ) throws SVNException { /* * Simple repository tree to create. Each entry will be * added in its own revision. */ String dirA = "dirA"; String dirB = "dirA/dirB"; String fileA = "dirA/fileA.txt"; String fileB = "dirA/dirB/fileB.txt"; byte[] fileAContents = "This is file fileA.txt".getBytes( ); byte[] fileBContents = "This is file fileB.txt".getBytes( ); SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator( ); long revision = -1; SVNCommitInfo info = null; String checksum = null; /* * First commit "/dirA". */ ISVNEditor editor = srcRepository.getCommitEditor( "adding " + dirA , null ); editor.openRoot( -1 ); editor.addDir( dirA , null , -1 ); editor.closeDir( ); editor.closeDir( ); info = editor.closeEdit( ); System.out.println( info ); revision = info.getNewRevision( ); /* * Then commit "/dirA/fileA.txt". */ editor = srcRepository.getCommitEditor( "adding " + fileA , null ); editor.openRoot( -1 ); editor.openDir( dirA , revision ); editor.addFile( fileA , null , -1 ); editor.applyTextDelta( fileA , null ); checksum = deltaGenerator.sendDelta( fileA , new ByteArrayInputStream( fileAContents ) , editor , true ); editor.closeFile( fileA , checksum ); editor.closeDir( ); editor.closeDir( ); info = editor.closeEdit( ); System.out.println( info ); revision = info.getNewRevision( ); /* * Then commit "/dirA/dirB". */ editor = srcRepository.getCommitEditor( "adding " + dirB , null ); editor.openRoot( -1 ); editor.openDir( dirA , revision ); editor.addDir( dirB , null , -1 ); editor.closeDir( ); editor.closeDir( ); editor.closeDir( ); info = editor.closeEdit( ); System.out.println( info ); revision = info.getNewRevision( ); /* * Then commit "/dirA/dirB/fileB.txt". */ editor = srcRepository.getCommitEditor( "adding " + fileB , null ); editor.openRoot( -1 ); editor.openDir( dirA , revision ); editor.openDir( dirB , revision ); editor.addFile( fileB , null , -1 ); editor.applyTextDelta( fileB , null ); checksum = deltaGenerator.sendDelta( fileB , new ByteArrayInputStream( fileBContents ) , editor , true ); editor.closeFile( fileB , checksum ); editor.closeDir( ); editor.closeDir( ); editor.closeDir( ); info = editor.closeEdit( ); System.out.println( info ); } }}} However if args are provided we must do some checks: * if the source url is a {{{file://}}} url and there's no repository at this place, we also create a new one and fill it up with some data * if the source url is a {{{file://}}} url and there's already a repository at this place and its latest revision is 0, we fill it with some data * in all other cases (non {{{file://}}} access and empty repositories accessible through {{{file://}}}) we use the data from the source repository {{{#!java public class Replicate { public static void main( String[] args ) { /* * Default values: * source and target repository paths */ String srcPath = "srcRepository"; String tgtPath = "tgtRepository"; String srcUrl = null; /* * Initializes the library (it must be done before ever using the * library itself) */ setupLibrary( ); if ( args != null ) { /* * a source repository url */ srcUrl = ( args.length >= 1 ) ? args[0] : srcUrl; /* * a target repository path */ tgtPath = ( args.length >= 2 ) ? args[1] : tgtPath; } SVNURL srcURL = null; SVNURL tgtURL = null; SVNRepository srcRepository = null; SVNRepository tgtRepository = null; boolean createSrcRepos = false; boolean populateSrcRepos = false; try { if ( srcUrl != null ) { /* * If a source url was provided, using it as a source repository */ srcURL = SVNURL.parseURIDecoded( srcUrl ); if ( "file".equals( srcURL.getProtocol( ) ) ) { File srcReposDir = new File( srcURL.getPath( ) ); if ( !srcReposDir.exists( ) ) { /* * it's a local access scheme and src path does not exist - * we'll need to create something */ createSrcRepos = true; populateSrcRepos = true; srcPath = srcURL.getPath( ); } else { srcRepository = SVNRepositoryFactory.create( srcURL ); if ( srcRepository.getLatestRevision( ) == 0 ) { /* * it's a local access scheme, src path already * exists, but seems to be an empty repository - * we'll need to create something in it */ populateSrcRepos = true; } } } } else { createSrcRepos = true; populateSrcRepos = true; } if ( createSrcRepos ) { srcURL = SVNRepositoryFactory.createLocalRepository( new File( srcPath ) , false , false ); } /* * For the target repository we need to enable revision property * changes. */ tgtURL = SVNRepositoryFactory.createLocalRepository(new File( tgtPath ) , true , false ); srcRepository = SVNRepositoryFactory.create( srcURL ); tgtRepository = SVNRepositoryFactory.create( tgtURL ); } catch ( SVNException svne ) { /* * getFullMessage() will give us a full tree of SVNException * errors. */ System.err.println( "Can not create a repository: " + svne.getErrorMessage( ).getFullMessage( ) ); System.exit( 1 ); } /* * Deault auth manager is used to cache a username in the * default Subversion credentials storage area. */ srcRepository.setAuthenticationManager( SVNWCUtil.createDefaultAuthenticationManager( ) ); tgtRepository.setAuthenticationManager( SVNWCUtil.createDefaultAuthenticationManager( ) ); try{ if ( populateSrcRepos ) { /* * Fills up the source repository with some data. */ populateSourceRepository( srcRepository ); } System.out.println( ); long srcLatestRevision = srcRepository.getLatestRevision( ); System.out.println( "The latest source revision: " + srcLatestRevision ); System.out.println( ); System.out.println( "'" + srcURL + "' repository tree:" ); System.out.println( ); /* * Using the DisplayRepositoryTree example to show the source * repository tree in the latest revision. */ DisplayRepositoryTree.listEntries( srcRepository , "" ); System.out.println( ); } catch( SVNException svne ) { System.err.println( "An error occurred while accessing source repository: " + svne.getErrorMessage( ).getFullMessage( ) ); System.exit( 1 ); } ... }}} First, we will try [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html#replay(long,%20long,%20boolean,%20org.tmatesoft.svn.core.io.ISVNEditor)| replay()]] functionality for replicating the source repository. We'll use a simple initialization function that copies revision props from revision 0 of the source repository to the same revision of the target one: {{{#!java private static void initializeRepository( SVNRepository fromRepos , SVNRepository toRepos ) throws SVNException { /* * Initialization means we need to set necessary svn:sync- properties * on revision 0 of the destination repository. But since our program * is just an example, we copy only revision properties from the * source repository to the destination one. */ copyRevisionProperties( fromRepos , toRepos , 0 ); } }}} The routine for copying revision properties: {{{#!java private static void copyRevisionProperties( SVNRepository fromRepository , SVNRepository toRepository , long revision ) throws SVNException { Map revProps = fromRepository.getRevisionProperties( revision , null ); for ( Iterator propNames = revProps.keySet( ).iterator( ); propNames.hasNext( ); ) { String propName = ( String ) propNames.next( ); String propValue = ( String ) revProps.get( propName ); toRepository.setRevisionPropertyValue( revision , propName , propValue ); } } }}} And synchronization itself: {{{#!java private static long synchronizeRepository( SVNRepository fromRepos , SVNRepository toRepos ) throws SVNException { long lastMergedRevision = 0; long fromLatestRevision = fromRepos.getLatestRevision( ); long count = 0; for ( long currentRev = lastMergedRevision + 1 ; currentRev <= fromLatestRevision ; currentRev++ ) { ISVNEditor commitEditor = toRepos.getCommitEditor( "" , null ); fromRepos.replay( 0 , currentRev , true , commitEditor ); SVNCommitInfo info = commitEditor.closeEdit( ); if ( info.getNewRevision( ) != currentRev ) { System.err.println( "Commit created rev " + info.getNewRevision( ) + " but should have created " + currentRev ); System.exit( 1 ); } System.out.println( "Committed revision " + info.getNewRevision( ) ); copyRevisionProperties( fromRepos , toRepos , currentRev ); count++; } return count; } }}} If we can't replicate the source repository in case a server does not support '''replay''' functionality we would like to use [[http://tmate.org/svn/kb/javadoc/org/tmatesoft/svn/core/replicator/SVNRepositoryReplicator.html|SVNRepositoryReplicator]]: {{{#!java private static long replicateRepository( SVNRepository srcRepository , SVNRepository tgtRepository ) throws SVNException { long latestRevision = srcRepository.getLatestRevision( ); SVNRepositoryReplicator replicator = SVNRepositoryReplicator.newInstance( ); //use this handler to print out progress information on commits replicator.setReplicationHandler( new ISVNReplicationHandler( ) { public void revisionReplicated( SVNRepositoryReplicator source , SVNCommitInfo commitInfo ) throws SVNException { System.out.println( "Committed revision " + commitInfo.getNewRevision( ) ); } public void revisionReplicating( SVNRepositoryReplicator source , SVNLogEntry logEntry ) throws SVNException { } public void checkCancelled( ) throws SVNCancelException { } }); return replicator.replicateRepository( srcRepository , tgtRepository , 1 , latestRevision ); } }}} In the main program we call this routines: {{{#!java ... try{ /* * First let's try the standard replay way. */ long replicatedRevisions = 0; try { initializeRepository( srcRepository , tgtRepository ); replicatedRevisions = synchronizeRepository( srcRepository , tgtRepository ); } catch ( SVNException svne ) { if ( svne.getErrorMessage( ).getErrorCode( ) != SVNErrorCode.RA_NOT_IMPLEMENTED ) { throw svne; } //...else the server does not support replay functionality tgtRepository = SVNRepositoryFactory.create( tgtURL ); replicatedRevisions = replicateRepository( srcRepository , tgtRepository ); } ... }}} If the server does not support '''replay''' functionality we get [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/SVNErrorCode.html#RA_NOT_IMPLEMENTED|SVNErrorCode.RA_NOT_IMPLEMENTED]] and invoke our replicator. {{{tgtRepository}}} is reinstantiated since it may be locked by its [[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html#getCommitEditor(java.lang.String,%20java.util.Map,%20boolean,%20org.tmatesoft.svn.core.io.ISVNWorkspaceMediator)| getCommitEditor()]] method. At the end we print out the number of replicated revisions and the target repository tree: {{{#!java ... System.out.println( ); System.out.println( "Number of replicated revisions: " + replicatedRevisions ); System.out.println( ); System.out.println( "'" + tgtURL + "' repository tree:" ); System.out.println( ); /* * Shows the tree of the target repository in the latest revision. */ DisplayRepositoryTree.listEntries( tgtRepository , "" ); }catch( SVNException svne ) { System.err.println( "An error occurred while accessing source repository: " + svne.getErrorMessage( ).getFullMessage( ) ); System.exit( 1 ); } } } }}} Running a program: {{{ r1 by 'me' at Wed Apr 26 02:10:14 NOVST 2006 r2 by 'me' at Wed Apr 26 02:10:14 NOVST 2006 r3 by 'me' at Wed Apr 26 02:10:15 NOVST 2006 r4 by 'me' at Wed Apr 26 02:10:15 NOVST 2006 The latest source revision: 4 'file:///G:/tgtRepository' repository tree: /dirA (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006) /dirA/dirB (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006) /dirA/dirB/fileB.txt (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006) /dirA/fileA.txt (author: 'me'; revision: 2; date: Wed Apr 26 02:10:14 NOVST 2006) Committed revision 1 Committed revision 2 Committed revision 3 Committed revision 4 Number of replicated revisions: 4 'file:///G:/tgtRepository' repository tree: /dirA (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006) /dirA/dirB (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006) /dirA/dirB/fileB.txt (author: 'me'; revision: 4; date: Wed Apr 26 02:10:15 NOVST 2006) /dirA/fileA.txt (author: 'me'; revision: 2; date: Wed Apr 26 02:10:14 NOVST 2006) }}} ---- Download the [[http://svn.svnkit.com/repos/svnkit/tags/1.3.5/doc/examples/src/org/tmatesoft/svn/examples/repository/Replicate.java| example program source code]].