Differences between revisions 1 and 2
Revision 1 as of 2007-02-19 11:53:47
Size: 21569
Editor: 193
Comment:
Revision 2 as of 2007-02-19 17:24:01
Size: 21395
Editor: 85
Comment:
Deletions are marked like this. Additions are marked like this.
Line 32: Line 32:
A user performs a recursive update to the latest (HEAD) revision on the root of the Working Copy. How does __SVNKit__ make a report of local
revisions in this case? First of all, reports are provided to an [SVNRepository | http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html] driver through an
[ISVNReporterBaton | http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/ISVNReporterBaton.html] implementation.
When you pass it to an update method of [SVNRepository | http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html] the latter one calls the baton's [report() | http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/ISVNReporterBaton.html#report(org.tmatesoft.svn.core.io.ISVNReporter)]
method passing it an __ISVNReporter__
object.
A user performs a recursive update to the latest (HEAD) revision on the root of the Working Copy. How does '''SVNKit''' make a report of local
revisions in this case? First of all, reports are provided to an [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html SVNRepository] driver through an
[http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/ISVNReporterBaton.html ISVNReporterBaton] implementation.
When you pass it to an update method of [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html SVNRepository] the latter one calls the baton's [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/ISVNReporterBaton.html#report(org.tmatesoft.svn.core.io.ISVNReporter) report()]
method passin
g it an '''ISVNReporter''' object.
Line 40: Line 40:
[{Java2HtmlPlugin {{{#!java
Line 53: Line 53:
}]

The {{repository}} driver passes an __ISVNReporter__ object to our reporter baton's {{report()}}
method. We are interested in what calls to an __ISVNReporter__ object our reporter baton should make to report local
}}}

The {{{repository}}} driver passes an '''ISVNReporter''' object to our reporter baton's {{{report()}}}
method. We are interested in what calls to an '''ISVNReporter''' object our reporter baton should make to report local
Line 59: Line 59:
[{Java2HtmlPlugin {{{#!java
Line 73: Line 73:
}]

Here we should stop for some important notes. The first call to __ISVNReporter__ is always {{setPath("" , ...)}},
i.e. we always start reporting with the item upon which an update is called. If {{target}} parameter of an
update method is {{null}} (like in our case) the {{setPath("", ...)}} call describes the node
corresponding to the location to which the driver is bound to (''/nodeA''). Otherwise if {{target}} is not {{null}},
it's a target item to update, and in this case
{{setPath("", ...)}} describes this target item. Note that
{{target}} is never a path but always a name. For example, if we wanted to update only ''itemA1'' we would
}}}

Here we should stop for some important notes. The first call to '''ISVNReporter''' is always {{{setPath("" , ...)}}},
i.e. we always start reporting with the item upon which an update is called. If {{{target}}} parameter of an
update method is {{{null}}} (like in our case) the {{{setPath("", ...)}}} call describes the node
corresponding to the location to which the driver is bound to ({{{/nodeA}}}). Otherwise if {{{target}}} is not {{{null}}},
it
's a target item to update, and in this case {{{setPath("", ...)}}} describes this target item. Note that
{{{target}}} is never a path but always a name. For example, if we wanted to update only {{{itemA1}}} we would
Line 83: Line 83:
[{Java2HtmlPlugin {{{#!java
Line 87: Line 87:
}] }}}
Line 91: Line 91:
[{Java2HtmlPlugin {{{#!java
Line 99: Line 99:
}]

There's no need to report ''/nodeA/nodeB/itemB'' since its current revision coincides with the revision of its parent
directory and it's not locally locked. By the same reason we don't report ''/nodeA/itemA1'', but we must report
''/nodeA/itemA2'' - although its revision is the same as the parent's one, but the item's locally locked, so maybe in a
}}}

There's no need to report {{{/nodeA/nodeB/itemB}}} since its current revision coincides with the revision of its parent
directory and it's not locally locked. By the same reason we don't report {{{/nodeA/itemA1}}}, but we must report
{{{/nodeA/itemA2}}} - although its revision is the same as the parent's one, but the item's locally locked, so maybe in a
Line 106: Line 106:
[{Java2HtmlPlugin {{{#!java
Line 116: Line 116:
}] }}}
Line 120: Line 120:
[{Java2HtmlPlugin {{{#!java
Line 130: Line 130:
}]

Although the local revision number of ''/nodeC/nodeD/'' coincides with the revision number of the parent we need to
}}}

Although the local revision number of {{{/nodeC/nodeD/}}} coincides with the revision number of the parent we need to
Line 135: Line 135:
[{Java2HtmlPlugin {{{#!java
Line 143: Line 143:
}]

''nodeF'' is absent from our Working Copy. This means that we haven't got it previously because we don't have sufficient
}}}

{{{nodeF}}} is absent from our Working Copy. This means that we haven't got it previously because we don't have sufficient
Line 149: Line 149:
''nodeG'' was locally deleted and commited in the 6-th revision, but the WC root has not been updated since then. Now {{{nodeG}}} was locally deleted and commited in the 6-th revision, but the WC root has not been updated since then. Now
Line 151: Line 151:
don't have ''nodeG'', so we don't need to report it, we wouldn't get it back in an update to revision 7. This is
because we report the WC root as in revision 4, but in revision 4 ''nodeG'' exists in the same state as in revision 7!
The case of absent nodes is similar: imagine that in revision 7 permission restrictions on ''nodeF'' were broken for
don't have {{{nodeG}}}, so we don't need to report it, we wouldn't get it back in an update to revision 7. This is
because we report the WC root as in revision 4, but in revision 4 {{{nodeG}}} exists in the same state as in revision 7!
The case of absent nodes is similar: imagine that in revision 7 permission restrictions on {{{nodeF}}} were broken for
Line 156: Line 156:
''nodeL'' is missing from our Working Copy, i.e. it's still under version control but has been erased in the file system {{{nodeL}}} is missing from our Working Copy, i.e. it's still under version control but has been erased in the file system
Line 158: Line 158:
missing file (''itemA3'') there's no need to get the entire file since a missing file can be restored from the base missing file ({{{itemA3}}}) there's no need to get the entire file since a missing file can be restored from the base
Line 164: Line 164:
[{Java2HtmlPlugin {{{#!java
Line 174: Line 174:
}]

''nodeH'' is incomplete what means that it was not updated entirely previous time (for example, an update operation was
}}}

{{{nodeH}}} is incomplete what means that it was not updated entirely previous time (for example, an update operation was
Line 184: Line 184:
[{Java2HtmlPlugin {{{#!java
Line 189: Line 189:
}] }}}
Line 194: Line 194:
[{Java2HtmlPlugin {{{#!java
Line 205: Line 205:
}]

If any method of __ISVNReporter__ throws an exception you should abort the reorter in the following way:

[{Java2HtmlPlugin
}}}

If any method of '''ISVNReporter''' throws an exception you should abort the reorter in the following way:

{{{#!java
Line 224: Line 224:
}]

!!!Receiving changes from a server-side
Now we know that when you call an update method of an [SVNRepository | http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html] driver, the driver first invokes your reporter
}}}

= Receiving changes from a server-side =
Now we know that when you call an update method of an [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html SVNRepository] driver, the driver first invokes your reporter
Line 230: Line 230:
[{Java2HtmlPlugin {{{#!java
Line 239: Line 239:
}] }}}
Line 250: Line 250:
[{Java2HtmlPlugin {{{#!java
Line 291: Line 291:
}] }}}
Line 297: Line 297:
[{Java2HtmlPlugin {{{#!java
Line 338: Line 338:
}] }}}
Line 342: Line 342:
[{Java2HtmlPlugin {{{#!java
Line 353: Line 353:
}] }}}
Line 357: Line 357:
[{Image src = 'Update_Tree2.png' caption = 'Updated Tree'}] attachment:Update_Tree2.png
Line 364: Line 364:
With a reporter (__ISVNReporter__) and an editor (__ISVNEditor__) you are not restricted by a Working Copy format.
The __SVNKit__ high-level engine implements an editor that stores a local data tree as directories and files within
With a reporter ('''ISVNReporter''') and an editor ('''ISVNEditor''') you are not restricted by a Working Copy format.
The '''SVNKit''' high-level engine implements an editor that stores a local data tree as directories and files within
Line 368: Line 368:
!!!Example: exporting a repository directory = Example: exporting a repository directory =
Line 370: Line 370:
administrative directories. This example demonstrates usage of __ISVNReporter__ and __ISVNEditor__ for exporting administrative directories. This example demonstrates usage of '''ISVNReporter''' and '''ISVNEditor''' for exporting
Line 375: Line 375:
[{Java2HtmlPlugin {{{#!java
Line 396: Line 396:
}] }}}
Line 400: Line 400:
[{Java2HtmlPlugin {{{#!java
Line 497: Line 497:
}] }}}
Line 501: Line 501:
[{Java2HtmlPlugin {{{#!java
Line 558: Line 558:
}] }}}
Line 561: Line 561:
Download the [example program source code | http://svn.svnkit.com/repos/svnkit/trunk/doc/examples/src/org/tmatesoft/svn/examples/repository/Export.java].

O
r view [the source code | Export.java Source File] in a separate page.
Download the [http://svn.svnkit.com/repos/svnkit/trunk/doc/examples/src/org/tmatesoft/svn/examples/repository/Export.java example program source code].

[:Committing To A Repository:<< Previous Page]

[:Replicating An Existing Repository:Next Page >>]

TableOfContents

Editing Operation: receiving changes from a repository

Another field of using [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/ISVNEditor.html ISVNEditor] is update operations (checkout, update, switch, export). But in update operations roles are just opposite to ones in commit operations: that is, while in a commit you obtain and use the library's editor to edit a repository, in an update you provide your own ISVNEditor implementation to the library, which will be called by a repository server to transmit changes to a client side. If a remote access to a repository is used, it means that according to a particular access protocol a server sends commands which are translated by SVNKit into calls to a client's editor.

So, a low-level update operation takes two main stages:

  • a client application describes the state of the local versioned items tree to a repository;
  • the infromation provided by the client is used by the server-side to decide what client items must be updated, such decisions are realized in calls to the client's editor.

In other words a client describes local revisions of the items to update, and a server traverses this tree to apply changes between what is in a repository and on the client's computer. This mechanism gives a certain freedom in choosing the format of storing local versioned tree, what means that you are not restricted by the bounds of a general Working Copy.

Reporting a local versioned tree state to a server-side

Descriptions of a local versioned tree state are made via the [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/ISVNReporter.html ISVNReporter] interface. We will study how to report local revisions by the example of using the low-level layer in the high-level one, i.e. we will describe how SVNKit reports revisions of Working Copy items using ISVNReporter.

Let's imagine we have got the following Working Copy tree:

attachment:Update_Tree1.png

A user performs a recursive update to the latest (HEAD) revision on the root of the Working Copy. How does SVNKit make a report of local revisions in this case? First of all, reports are provided to an [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html SVNRepository] driver through an [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/ISVNReporterBaton.html ISVNReporterBaton] implementation. When you pass it to an update method of [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html SVNRepository] the latter one calls the baton's [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/ISVNReporterBaton.html#report(org.tmatesoft.svn.core.io.ISVNReporter) report()] method passing it an ISVNReporter object.

Updating to the HEAD revision:

   1     ...
   2     FSRepositoryFactory.setup( );
   3     String url = "file://localhost/rep/nodeA/";
   4 
   5     SVNRepository repository = SVNRepositoryFactory.create( SVNURL.parseURIDecoded( url ) );
   6     ReporterBaton reporterBaton = ...;
   7     ISVNEditor editor = ...;
   8     
   9     repository.update( -1 /*forces to use the latest revision*/ , null /*target*/ , true /*recursive*/ , reporterBaton , editor );
  10     ...

The repository driver passes an ISVNReporter object to our reporter baton's report() method. We are interested in what calls to an ISVNReporter object our reporter baton should make to report local revisions in our example on condition that a user calls a recursive update on the root of our Working Copy:

   1 ...
   2 public class ReporterBaton implements ISVNReporterBaton {
   3 
   4     ...
   5     
   6     public void report( ISVNReporter reporter ) throws SVNException {
   7         
   8         //for the WC root
   9         reporter.setPath( "" /*path*/ , null /*lockToken*/ , 4 /*revision*/ , false /*startEmpty*/ );
  10 
  11         ...

Here we should stop for some important notes. The first call to ISVNReporter is always setPath("" , ...), i.e. we always start reporting with the item upon which an update is called. If target parameter of an update method is null (like in our case) the setPath("", ...) call describes the node corresponding to the location to which the driver is bound to (/nodeA). Otherwise if target is not null, it's a target item to update, and in this case setPath("", ...) describes this target item. Note that target is never a path but always a name. For example, if we wanted to update only itemA1 we would call:

   1     repository.update( -1 /*revision*/ , "itemA1" /*target*/ , false /*recursive*/ , reporterBaton , editor );

Further we traverse our tree:

   1         ...
   2 
   3         reporter.setPath( "nodeB" , null , 5 , false );
   4 
   5         ...

There's no need to report /nodeA/nodeB/itemB since its current revision coincides with the revision of its parent directory and it's not locally locked. By the same reason we don't report /nodeA/itemA1, but we must report /nodeA/itemA2 - although its revision is the same as the parent's one, but the item's locally locked, so maybe in a repository the lock is broken:

   1         ...
   2 
   3         //provide the item's lock token
   4         String lockToken = ...;
   5         reporter.setPath( "itemA2" , lockToken , 4 , false );
   6 
   7         ...

Continuing our report:

   1         ...
   2 
   3         SVNURL url = SVNURL.parseURIDecoded( "file://localhost/rep/node2A/nodeC/nodeD/" );
   4         //switched paths are described in this way:
   5         reporter.linkPath( url , "nodeC/nodeD" , null /*lockToken*/ , 4 /*revision*/ , false /*startEmpty*/ );
   6 
   7         ...

Although the local revision number of /nodeC/nodeD/ coincides with the revision number of the parent we need to report it as it's switched to another location of the repository.

   1         ...
   2         reporter.deletePath( "nodeF" );
   3         reporter.deletePath( "nodeG" );
   4         reporter.deletePath( "nodeL" );
   5         ...

nodeF is absent from our Working Copy. This means that we haven't got it previously because we don't have sufficient permissions on this directory. Furher we'll discuss how we get aware of absent directories and files when talking about editor invocations.

nodeG was locally deleted and commited in the 6-th revision, but the WC root has not been updated since then. Now imagine that someone returned that directory in revision 7 in the state it was in revision 4 . If we considered that we don't have nodeG, so we don't need to report it, we wouldn't get it back in an update to revision 7. This is because we report the WC root as in revision 4, but in revision 4 nodeG exists in the same state as in revision 7! The case of absent nodes is similar: imagine that in revision 7 permission restrictions on nodeF were broken for you, but you won't get the directory if you don't say you don't have it.

nodeL is missing from our Working Copy, i.e. it's still under version control but has been erased in the file system by a mistake. The idea is the same - we want to get the entire directory back into the Working Copy. However for a missing file (itemA3) there's no need to get the entire file since a missing file can be restored from the base (clear or unchanged) revision residing in an administrative folder (.svn) of the parent directory.

This is why it's important to report deleted and absent nodes when their local revisions are different from the parent's one as well as report missing directories.

   1         ...
   2 
   3         reporter.setPath( "nodeH" , null , 4 , true /*startEmpty*/ );
   4         reporter.setPath( "nodeH/nodeH2" , null , 4 , false /*startEmpty*/ );
   5         reporter.setPath( "nodeH/itemH" , null , 5 , false /*startEmpty*/ );
   6 
   7         ...

nodeH is incomplete what means that it was not updated entirely previous time (for example, an update operation was interrupted due to network connection problems or a server breakdown). So, we report incomplete directories as empty. And also we must report all children entries in an incomplete directory, doesn't matter whether their revisions are different from the parent's revision or not.

Another case of reporting a node as being empty is a checkout operation when initially you have no entries. In this case you make a single call to a reporter:

   1         long rev = ...;
   2         reporter.setPath( "" , null , rev , true );

Well, that's all for our example Working Copy tree. Items scheduled for either addition or deletion are not reported. We are finished:

   1         ...
   2         
   3         //called at the end of a report
   4         reporter.finishReport( );
   5         
   6         ...
   7     }
   8 }

If any method of ISVNReporter throws an exception you should abort the reorter in the following way:

   1         ...
   2         
   3         try {
   4             ...        
   5             reporter.setPath( ... );
   6             ...
   7         } catch( SVNException svne ) {
   8             reporter.abortReport( );
   9             ...
  10         }
  11         
  12         ...

Receiving changes from a server-side

Now we know that when you call an update method of an [http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepository.html SVNRepository] driver, the driver first invokes your reporter baton to get a client's report of a local tree state:

   1     ...
   2 
   3     ISVNReporter reporter = ...;
   4     reporterBaton.report( reporter );
   5 
   6     ...

If our local tree (Working Copy, for instance) is successfully reported the diver calculates changes between our tree contents and what is in the repository. The driver passes these changes to the caller's editor as it traverses the tree. In other words, the driver edits our tree in a hierarchical way.

Let's proceed with our example. We have discussed how we should report our example Working Copy tree. Now we'll speak of how a server-side invokes our editor. First of all, it sets the actual revision which a local tree will be updated to, then opens the root node (the root of the Working Copyin in our case) and traverses the tree approximately like this:

   1     ...
   2     //let HEAD revision be 7
   3     editor.targetRevision( 7 );
   4 
   5     //gives the source revision we provided in our report
   6     editor.openRoot( 4 );
   7 
   8     //in revision 7 properties were added for nodeB
   9     editor.openDir( "nodeB" , 5 );
  10     editor.changeDirProperty( "prop1" , "val1" );
  11     editor.changeDirProperty( "prop2" , "val2" );
  12     editor.closeDir( );
  13 
  14     ...
  15     
  16     //receiving changes for a switched node - nodeD
  17     editor.openDir( "nodeC" , 4 );
  18     editor.openDir( "nodeC/nodeD" , 4 );
  19     //itemD2 was added under /node2A/nodeC/nodeD/ in the repository
  20     editor.addFile( "nodeC/nodeD/itemD2" , null , -1 );
  21     editor.applyTextDelta( "nodeC/nodeD/itemD2" , null );
  22     editor.textDeltaChunk( "nodeC/nodeD/itemD2" , window1 );
  23     ...
  24     editor.textDeltaEnd( "nodeC/nodeD/itemD2" );
  25     //text checksum
  26     editor.closeFile( "nodeC/nodeD/itemD2" , checksum );
  27     //closing nodeC/nodeD
  28     editor.closeDir( );
  29     //closing nodeC
  30     editor.closeDir( );
  31 
  32     ...
  33     
  34     //we are still not permitted to read /nodeA/nodeF,
  35     //this is how a server lets us know about this
  36     editor.absentDir( "nodeF" );
  37     
  38     ...

All items which are located under an incomplete directory and have got the same revision as the incomplete parent's one are ADDED once again. But those items that have got revisions different from the incomplete parent's one will rather receive differences.

   1     editor.openDir( "nodeH" , 4 );
   2     editor.addDir( "nodeH/nodeH2" , null , -1 );
   3     //closing nodeH/nodeH2
   4     editor.closeDir( );
   5 
   6     editor.addDir( "nodeH/nodeH3" , null , -1 );
   7     //closing nodeH/nodeH3
   8     editor.closeDir( );
   9 
  10     editor.addFile( "nodeH/itemH2" , null , -1 );
  11     editor.applyTextDelta( "nodeH/itemH2" , null );
  12     //sending delta windows
  13     ...
  14     editor.textDeltaEnd( "nodeH/itemH2" );
  15     editor.closeFile( "nodeH/itemH2" , checksum );
  16 
  17     //receiving changes for nodeH/itemH
  18     editor.openFile( "nodeH/itemH" , 5 );
  19     editor.applyTextDelta( "nodeH/itemH" , baseChecksum );
  20     //sending delta windows
  21     ...
  22     editor.textDeltaEnd( "nodeH/itemH" );
  23     editor.closeFile( "nodeH/itemH" , checksum );
  24     //closing nodeH
  25     editor.closeDir( );
  26     ...
  27     
  28     //the lock on itemA2 was broken in the repository
  29     editor.changeFileProperty( "itemA2" , SVNProperty.LOCK_TOKEN , null );
  30     
  31     ...
  32     
  33     //receiving a missing node - /nodeA/nodeL
  34     editor.addDir( "nodeL" , null , -1 );
  35     ...
  36     editor.closeDir( );
  37     
  38     ...

And so forth.

   1     ...
   2     
   3     //closes the WC root
   4     editor.closeDir( );
   5     //finishes editing
   6     ediotr.closeEdit( );
   7     
   8     ...

The update is finished. Now our Working Copy looks like this:

attachment:Update_Tree2.png

This is how a local tree is traversed and applied changes coming from a repository. To a certain extent, this is only an example, a scheme. Besides versioned properties files as well as directories receive some metadata - unmanaged (by a user) properties used for version control. We don't show them in our demonstration code. Nevertheless the main idea is correct.

With a reporter (ISVNReporter) and an editor (ISVNEditor) you are not restricted by a Working Copy format. The SVNKit high-level engine implements an editor that stores a local data tree as directories and files within a Working Copy, but you can choose a different format of saving received versioned data for your editor.

Example: exporting a repository directory

In Subversion export is like checkout except that exported directories are clean, not versioned since they don't have administrative directories. This example demonstrates usage of ISVNReporter and ISVNEditor for exporting a directory from a repository.

We implement the following reporter that reports our local tree as being empty:

   1 public class ExportReporterBaton implements ISVNReporterBaton {
   2 
   3     private long exportRevision;
   4         
   5     public ExportReporterBaton( long revision ){
   6         exportRevision = revision;
   7     }
   8         
   9     public void report( ISVNReporter reporter ) throws SVNException {
  10         try {
  11             reporter.setPath( "" , null , exportRevision , true );
  12             reporter.finishReport( );
  13         } catch( SVNException svne ) {
  14             reporter.abortReport( );
  15             System.out.println( "Report failed" );
  16         }
  17     }
  18 }

And the editor which performs minimal work to save a coming versioned tree as files and directories:

   1 public class ExportEditor implements ISVNEditor {
   2         
   3     private File myRootDirectory;
   4     private SVNDeltaProcessor myDeltaProcessor;
   5         
   6     public ExportEditor( File root ) {
   7         myRootDirectory = root;
   8 
   9         /*
  10          * Utility class that will help us to transform 'deltas' sent by the 
  11          * server to the new file contents.  
  12          */
  13         myDeltaProcessor = new SVNDeltaProcessor( );
  14     }
  15 
  16     public void targetRevision( long revision ) throws SVNException {
  17     }
  18 
  19     public void openRoot( long revision ) throws SVNException {
  20     }
  21         
  22     public void addDir( String path , String copyFromPath , long copyFromRevision ) throws SVNException {
  23         File newDir = new File( myRootDirectory , path );
  24         if ( !newDir.exists( ) ) {
  25             if ( !newDir.mkdirs( ) ) {
  26                 SVNErrorMessage err = SVNErrorMessage.create( SVNErrorCode.IO_ERROR , "error: failed to add the directory ''{0}''." , newDir );
  27                 throw new SVNException( err );
  28             }
  29         }
  30         System.out.println( "dir added: " + path );
  31     }
  32         
  33     public void openDir( String path , long revision ) throws SVNException {
  34     }
  35 
  36     public void changeDirProperty( String name , String value ) throws SVNException {
  37     }
  38 
  39     public void addFile( String path , String copyFromPath , long copyFromRevision ) throws SVNException {
  40         File file = new File( myRootDirectory , path );
  41         if ( file.exists( ) ) {
  42             SVNErrorMessage err = SVNErrorMessage.create( SVNErrorCode.IO_ERROR , "error: exported file ''{0}'' already exists!" , file );
  43             throw new SVNException( err );
  44         }
  45 
  46         try {
  47             file.createNewFile( );
  48         } catch ( IOException e ) {
  49             SVNErrorMessage err = SVNErrorMessage.create( SVNErrorCode.IO_ERROR , "error: cannot create new  file ''{0}''" , file );
  50             throw new SVNException( err );
  51         }
  52     }
  53         
  54     public void openFile( String path , long revision ) throws SVNException {
  55     }
  56 
  57     public void changeFileProperty( String path , String name , String value ) throws SVNException {
  58     }        
  59 
  60     public void applyTextDelta( String path , String baseChecksum ) throws SVNException {
  61         myDeltaProcessor.applyTextDelta( null , new File( myRootDirectory , path ) , false );
  62     }
  63 
  64     public OutputStream textDeltaChunk( String path , SVNDiffWindow diffWindow )   throws SVNException {
  65         return myDeltaProcessor.textDeltaChunk( diffWindow );
  66     }
  67         
  68     public void textDeltaEnd(String path) throws SVNException {
  69         myDeltaProcessor.textDeltaEnd( );
  70     }
  71         
  72     public void closeFile( String path , String textChecksum ) throws SVNException {
  73         System.out.println( "file added: " + path );
  74     }
  75 
  76     public void closeDir( ) throws SVNException {
  77     }
  78 
  79     public void deleteEntry( String path , long revision ) throws SVNException {
  80     }
  81         
  82     public void absentDir( String path ) throws SVNException {
  83     }
  84 
  85     public void absentFile( String path ) throws SVNException {
  86     }        
  87         
  88     public SVNCommitInfo closeEdit( ) throws SVNException {
  89         return null;
  90     }
  91         
  92     public void abortEdit( ) throws SVNException {
  93     }
  94 }

Having got these two implementations we export a directory from a world-readable repository:

   1 public class Export {
   2     
   3     public static void main( String[] args ) { 
   4         DAVRepositoryFactory.setup( );
   5         SVNURL url = SVNURL.parseURIEncoded( "http://svn.svnkit.com/repos/svnkit/trunk/doc" );
   6         String userName = "foo";
   7         String userPassword = "bar";
   8         
   9         //Prepare filesystem directory (export destination).
  10         File exportDir = new File( "export" );
  11         if ( exportDir.exists( ) ) {
  12             SVNErrorMessage err = SVNErrorMessage.create( SVNErrorCode.IO_ERROR , "Path ''{0}'' already exists" , exportDir );
  13             throw new SVNException( err );
  14         }
  15         exportDir.mkdirs( );
  16 
  17         SVNRepository repository = SVNRepositoryFactory.create( url );
  18 
  19         ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager( userName, userPassword );
  20         repository.setAuthenticationManager( authManager );
  21 
  22         SVNNodeKind nodeKind = repository.checkPath( "" , -1 );
  23         if ( nodeKind == SVNNodeKind.NONE ) {
  24             SVNErrorMessage err = SVNErrorMessage.create( SVNErrorCode.UNKNOWN , "No entry at URL ''{0}''" , url );
  25             throw new SVNException( err );
  26         } else if ( nodeKind == SVNNodeKind.FILE ) {
  27             SVNErrorMessage err = SVNErrorMessage.create( SVNErrorCode.UNKNOWN , "Entry at URL ''{0}'' is a file while directory was expected" , url );
  28             throw new SVNException( err );
  29         }
  30 
  31         //Get latest repository revision. We will export repository contents at this very revision.
  32         long latestRevision = repository.getLatestRevision( );
  33         
  34         ISVNReporterBaton reporterBaton = new ExportReporterBaton( latestRevision );
  35         
  36         ISVNEditor exportEditor = new ExportEditor( exportDir );
  37         
  38         /*
  39          * Now ask SVNKit to perform generic 'update' operation using our reporter and editor.
  40          * 
  41          * We are passing:
  42          * 
  43          * - revision from which we would like to export
  44          * - null as "target" name, to perform export from the URL SVNRepository was created for, 
  45          *   not from some child directory.
  46          * - reporterBaton
  47          * - exportEditor.  
  48          */
  49         repository.update( latestRevision , null , true , reporterBaton , exportEditor );
  50         
  51         System.out.println( "Exported revision: " + latestRevision );
  52     
  53     }
  54 }


Download the [http://svn.svnkit.com/repos/svnkit/trunk/doc/examples/src/org/tmatesoft/svn/examples/repository/Export.java example program source code].

Updating_From_A_Repository (last edited 2012-01-03 18:05:02 by ip-109-80-120-205)