The only pure Java™ Subversion library in the world!
Home Get Library Knowledge Base Licensing

Using ISVNReporter/ISVNEditor in update-related operations

The following article shortly describes the mechanism of an update-related operation (update, check out, switch, etc.) in terms of the SVNKit low-level API (org.tmatesoft.svn.core.io package). The article is rather dedicated to using ISVNReporter/ISVNEditor interfaces which are the key 'figures' in those operations.

How to use ISVNReporter

When SVNRepository.update(..) is called ISVNReporterBaton.report(..) is invoked to make reports (with the help of ISVNReporter) of the working files/directories state. For example, if the scope is to update existing local items to some definite revision (that is passed to SVNRepository.update(..)) ISVNReporterBaton.report(..) should describe to the server revisions of all the items (files, directories) if their revisions differ from the update target revision. Suppose you have got the following tree you have somewhen checked out from a repository:

  /(4)
  /dirA(5)/
          /dirB(6)/
                  /file1.txt(6)
          /dirC(4)/
                  /file2.txt(5)
          /file3.txt(7)

Numbers in brackets correspond to revisions. Well, you see that the root directory has the revision number = 4, dirA - 5, dirB - 6 and so on. When you call SVNRepository.update(..) to bring the root directory up to date say to revision 8 you should describe the revisions of all the entries top-down for each directory (starting with the root):

import org.tmatesoft.svn.core.io.ISVNReporterBaton;
import org.tmatesoft.svn.core.io.ISVNReporter;

public class MyReporterBaton implements ISVNReporterBaton {
  public void report(ISVNReporter reporter) throws SVNException {

	//for the root directory
	reporter.setPath("", null, 4, false);
	
	//for "/dirA"
	reporter.setPath("/dirA", null, 5, false);
	
	//for "/dirA/dirB"
	reporter.setPath("/dirA/dirB", null, 6, false);
		
	//for "/dirA/dirB/file1.txt"
	reporter.setPath("/dirA/dirB/file1.txt", null, 6, false);

	//for "/dirA/dirC"
	reporter.setPath("/dirA/dirC", null, 4, false);
    
	....//and so on for all entries which revisions differ from 8

	/* always called at the end of the report - when the state of the 
	 * entire tree is described.
	 */
	reporter.finishReport();
  }
}

Several significant moments:

  • if some file(s) was(were) locked then you should provide the lock-token for that file(s) as the 2nd parameter of ISVNReporter.setPath(..). For example:
    	/(4)
    	/file.txt(7, locked)
    
    file.txt is at the 7th revision and was locked.
    	reporter.setPath("/file.txt", lockToken, 7, false);
    
    Even though the revision of the locked file is the same as the target update revision ISVNReporter.setPath(..) is called for such file anyway.
  • The last parameter of ISVNReporter.setPath(..) - boolean flag startEmpty - must be true for a checkout. Also it's set to true for those directories that were not successfully updated at a previous time due to errors (if such situation had a place). So, startEmpty = true means the directory is still empty. In other cases it must be false.
  • If a user's local item (file/directory) is updated against a URL different from that it was checked out from (being switched in other words), then instead of calling reporter.setPath(..) you should call:
    import org.tmatesoft.svn.core.SVNURL;
    ...    
        reporter.linkPath(newRepositoryLocation, path, 
                          lockToken, revision, 
                          false);
    
    newRepositoryLocation is an SVNURL instance which is the new parent root (meaning path is relative to this root since this moment). That's the only difference between just an update and a switch.
  • If an item was scheduled for deletion or if it's a missing directory (that was accidentally deleted) then you should call:
    	//report that the path was deleted
    	reporter.deletePath(path);
    
  • At the end of the report call ISVNReporter.finishReport() that denotes the end of the report.
  • One more important moment: if during a report an exception occured - the report failed - call ISVNReporter.abortReport() that properly finishes the report in a case of a fault.

How to use ISVNEditor

When ISVNReporterBaton has finished his work and the server knows everything of the user's local versioned items (dirs and files) state it sends to the client commands as well as data (file/dir properties and file delta) to bring client's items up to date. These commands are translated into calls to ISVNEditor methods.

Suppose there's the following local tree which is being updated recursively (starting with the directory the update was initiated on and moving deep down) to 8th revision:

    /(5)
    /dirA(5)/
            /file1.txt(5)
            /file2.txt(5)
Assume that only file1.txt is out of date and must be updated. The server sends commands to update the file which are translated into series of calls to ISVNEditor methods. Here is the scheme of this process (an implementor himself doesn't make these calls, his aim is only to provide an ISVNEditor implementation to SVNRepository.update(..) method; the following is rather an illustration describing how the SVNKit library invokes ISVNEditor methods):
     //sets the target revision the copy is being updated to.
     editor.targetRevision(revision);

     /* processing starts with the parent directory the update was
      * run for - "/"; now modifications can be applied to the opened 
      * directory.
      */
     editor.openRoot(revision);

     //changing root directory properties
     editor.changeDirProperty(propertyName1, propertyValue1);
     editor.changeDirProperty(propertyName2, propertyValue2);
    
     .....................................
    
     //opens "/dirA".
     editor.openDir("/dirA", revision);

     /* now modifications can be applied to the opened directory. 
      * For example, changing its properties.
      * Also all further calls like editor.openFile(..), 
      * editor.addFile(..) or editor.openDir(..), editor.addDir(..) 
      * are relative to the currently opened directory.
      */
     editor.changeDirProperty(propertyName1, propertyValue1);
     editor.changeDirProperty(propertyName2, propertyValue2);

     .....................................

     //opens file "file1.txt" to modify it
     editor.openFile("/dirA/file1.txt", revision);
 		
     //changing properties of "file1.txt"
     editor.changeFileProperty("/dirA/file1.txt", propertyName1, propertyValue1);
     editor.changeFileProperty("/dirA/file1.txt", propertyName2, propertyValue2);
     
     .....................................

     /* file contents are out of date - the server sends delta
      * (the difference between the local BASE-revision copy and 
      * the file in the repository).
      * baseChecksum is provided by the server to make certain of 
      * the delta will be applied correctly - the client should 
      * compare it with his own one evaluated upon the contents of 
      * the file at the BASE revision - that is the state of the file 
      * it had just after the previous update (or checkout). If both 
      * checksums match each other - it's ok, the delta can be applied 
      * correctly, if don't - may be the local file is
      * corrupted, that's an error.
      */
     editor.applyTextDelta("/dirA/file1.txt", baseChecksum);

     /* well, if the previous step was ok, the next step is to receive 
      * the delta itself. ISVNEditor.textDeltaChunk(..) receives 
      * an SVNDiffWindow - this is an object which contains instructions 
      * on how the delta (the entire delta or a part of it when the delta 
      * is too big) must be applied. If the delta is too big 
      * ISVNEditor.textDeltaChunk(..) is called several times to pass all 
      * parts of the delta; in this case all passed diffWindows should be 
      * accumulated and associated with their OutputStreams (each call to 
      * ISVNEditor.textDeltaChunk(..) returns an OutputStream as a storage 
      * where delta is written).
      */
     OutputStream os = editor.textDeltaChunk("/dirA/file1.txt", diffWindow);
 
     /* the following is called when all the delta is received.
      * that is where it's applied to a local file; in this illustration
      * "file1.txt" is modified.
      */
     editor.textDeltaEnd("/dirA/file1.txt");
 		
     /* the final point of the file modification: once again the server 
      * sends a checksum to control if the resultant file ("file1.txt") 
      * was modified correctly; the client repeats the operation of 
      * comparing the got checksum with the own one evaluated upon the 
      * resultant file contents.
      */ 
     editor.closeFile("/dirA/file1.txt", textChecksum);

     //closes the directory  - "/dirA"
     editor.closeDir();
 
     //closes the root directory - "/"
     editor.closeDir();

     /* editing ends with a call to ISVNEditor.closeEdit() which returns
      * the commit information (SVNCommitInfo) - what revision the copy is
      * updated to, who is the author of the changes, when the changes were
      * committed.
      */   
     SVNCommitInfo commitInfo = editor.closeEdit();
 

That is how an update runs in common words. The described scheme is analogous for the case when more than one file is out of date (what is more actual in reality), the local copy tree is processed (by an ISVNEditor) top-down for each directory and file that must be updated.

Well, the case of a checkout is a little bit different. ISVNEditor.openDir(..) and ISVNEditor.openFile(..) are not called, instead ISVNEditor.addDir(..), ISVNEditor.addFile(..) are called for each directory and file to be checked out. This is a principal model of how ISVNEditor methods are invoked during a checkout (suppose we are checking out some node tree from a repository what will lead us to the previous illustrartion when the local copy already exists):

     //sets the target revision of the copy being checked out.
     editor.targetRevision(revision);

     /* ISVNEditor.openRoot is not called;
      * setting root directory properties.
      */
     editor.changeDirProperty(propertyName, propertyValue);

     /* adds "/dirA". copyDirFromPath & copyFromRevision - are irrelevant 
      * in an update editor.
      * If you want to have a compatibility with the native Subversion 
      * command line client in the case of a checkout (not simply an export) 
      * an implementation of this method should create an administrative 
      * area (.svn directory) for the directory being added ("/dirA").
      */ 
     editor.addDir("/dirA", copyDirFromPath, CopyFromRevision);

     /* setting the directory properties - they may be stored in the 
      * previously created .svn directory (SVN command line client 
      * compatibility).
      * Also all further calls like editor.addFile(..)/editor.addDir(..)
      * are relative to the added directory.
      */
     editor.changeDirProperty(propertyName1, propertyValue1);
     editor.changeDirProperty(propertyName2, propertyValue2);

     .....................................

     /* adds file "file1.txt". copyFileFromPath & copyFromRevision - are 
      * irrelevant in an update editor.
      */
     editor.addFile("/dirA/file1.txt", copyFileFromPath, CopyFromRevision);

     /* setting properties of "file1.txt". May be saved in .svn 
      * directory (for compatibility with the native SVN command 
      * line client).
      */
     editor.changeFileProperty("/dirA/file1.txt", propertyName1, propertyValue1);
     editor.changeFileProperty("/dirA/file1.txt", propertyName2, propertyValue2);
     
     .....................................

     /* if the file is empty  - only ISVNEditor.applyTextDelta(..) 
      * and ISVNEditor.textDeltaEnd(..) are called. Otherwise 
      * ISVNEditor.textDeltaChunk(..) is also invoked.
      * baseChecksum is irrelevant for a checkout.
      */
     editor.applyTextDelta("/dirA/file1.txt", baseChecksum);

     //writing file contents (delta is contents in this case).
     OutputStream os1 = editor.textDeltaChunk("/dirA/file1.txt", diffWindow1);

     //if "file1.txt" is too big...
     OutputStream os2 = editor.textDeltaChunk("/dirA/file1.txt", diffWindow2);

     .....................................

     //all contents are received, "file1.txt" can be created
     editor.textDeltaEnd("/dirA/file1.txt");

     /* the final point of the file modification: once again the 
      * server sends a checksum to control if the resultant file 
      * ("file1.txt") was transmitted and constructed correctly; 
      * the client compares the got checksum with the own one 
      * evaluated upon the resultant file contents.
      * It may be this method implementation where a copy of the 
      * file ("file1.txt") - the BASE revision file copy - is 
      * stored in .svn directory (for compatibility with the 
      * native SVN command line client).
      */ 
     editor.closeFile("/dirA/file1.txt", textChecksum);

     /* again if you want to have a compatibility with the native 
      * SVN command line client each entry (wheteher it's a file 
      * or a directory) that is added should be reflected in its 
      * parent's administrative directory - .svn
      */

     //adds file "file2.txt".  
     editor.addFile("/dirA/file2.txt", copyFileFromPath, CopyFromRevision);

     ............................................//so on

     //closes the directory  - "/dirA"
     editor.closeDir();

     //closes the root directory - "/"
     editor.closeDir();

     SVNCommitInfo commitInfo = editor.closeEdit();


If you have any questions regarding SVNKit, would like to report a bug or contribute a patch, please write to support@svnkit.com


Java™ and all Java-based marks are a trademark or registered trademark of Sun Microsystems, Inc, in the United States and other countries. TMate Software and the website svnkit.com are independent of Sun Microsystems, Inc. and have no relationship, formal or informal.