Main


Managing A Working Copy

Finally we came to discussing the SVNKit high-level API - the one for manipulating Working Copies. All Working Copy operations are logically organized in different SVN*Client classes. At the same time there's SVNClientManager class which composes all SVN*Client classes and, thus, simplifies work on creating and keeping various client classes:

WC_Class_Diagram.png

So, you may instantiate different SVN*Client classes separately or keep a single SVNClientManager for the same purposes. In fact, SVNClientManager instantiates each SVN*Client object at the first time you request it, but not earlier.

Methods of each SVN*Client class are similar to commands of the Subversion command line client. We will show several simple operations you can perform upon Working Copies using SVNKit. We write a class WorkingCopy which utilizes an SVNClientManager object:

   1 ...
   2 
   3 public class WorkingCopy {
   4 
   5     private static SVNClientManager ourClientManager;
   6     ...

Then go several demonstration functions that will be used in the main program. The following one creates a new directory immediately in a repository:

   1     private static SVNCommitInfo makeDirectory( SVNURL url , String commitMessage ) throws SVNException {
   2         return ourClientManager.getCommitClient( ).doMkDir( new SVNURL[] { url } , commitMessage );
   3     }

This one imports a local directory to a repository:

   1     private static SVNCommitInfo importDirectory( File localPath , SVNURL dstURL , String commitMessage , boolean isRecursive ) throws SVNException {
   2         return ourClientManager.getCommitClient( ).doImport( localPath , dstURL , commitMessage , isRecursive );
   3     }

This one recursively commits Working Copy modifications to a repository:

   1     private static SVNCommitInfo commit( File wcPath , boolean keepLocks , String commitMessage ) throws SVNException {
   2         return ourClientManager.getCommitClient().doCommit( new File[] { wcPath } , keepLocks , commitMessage , false , true );
   3     }

This one checks out a Working Copy given a repository url:

   1     private static long checkout( SVNURL url , SVNRevision revision , File destPath , boolean isRecursive ) throws SVNException {
   2 
   3         SVNUpdateClient updateClient = ourClientManager.getUpdateClient( );
   4         /*
   5          * sets externals not to be ignored during the checkout
   6          */
   7         updateClient.setIgnoreExternals( false );
   8         /*
   9          * returns the number of the revision at which the working copy is 
  10          */
  11         return updateClient.doCheckout( url , destPath , revision , revision , isRecursive );
  12     }

This one updates a Working Copy to a particular revision:

   1     private static long update( File wcPath , SVNRevision updateToRevision , boolean isRecursive ) throws SVNException {
   2 
   3         SVNUpdateClient updateClient = ourClientManager.getUpdateClient( );
   4         /*
   5          * sets externals not to be ignored during the update
   6          */
   7         updateClient.setIgnoreExternals( false );
   8         /*
   9          * returns the number of the revision wcPath was updated to
  10          */
  11         return updateClient.doUpdate( wcPath , updateToRevision , isRecursive );
  12     }

This one switches a Working Copy to another url:

   1     private static long switchToURL( File wcPath , SVNURL url , SVNRevision updateToRevision , boolean isRecursive ) throws SVNException {
   2         SVNUpdateClient updateClient = ourClientManager.getUpdateClient( );
   3         /*
   4          * sets externals not to be ignored during the switch
   5          */
   6         updateClient.setIgnoreExternals( false );
   7         /*
   8          * returns the number of the revision wcPath was updated to
   9          */
  10         return updateClient.doSwitch( wcPath , url , updateToRevision , isRecursive );
  11     }

This one recursively adds an existing local item under version control (schedules for addition):

   1     private static void addEntry( File wcPath ) throws SVNException {
   2         ourClientManager.getWCClient( ).doAdd( wcPath , false , false , false , true );
   3     }

This one locks a versioned item:

   1     private static void lock( File wcPath , boolean isStealLock , String lockComment ) throws SVNException {
   2         ourClientManager.getWCClient( ).doLock( new File[] { wcPath } , isStealLock , lockComment );
   3     }

This one deletes a versioned item from version control (schedules for deletion):

   1     private static void delete( File wcPath , boolean force ) throws SVNException {
   2         ourClientManager.getWCClient( ).doDelete( wcPath , force , false );
   3     }

This function copies or moves one location to another one within the same repository:

   1     private static SVNCommitInfo copy( SVNURL srcURL , SVNURL dstURL , boolean isMove , String commitMessage ) throws SVNException {
   2         return ourClientManager.getCopyClient().doCopy( srcURL , SVNRevision.HEAD , dstURL , isMove , commitMessage );
   3     }

We also need a function which will report us status (including remote status) of our Working Copy. The high-level API introduces several types of handlers to procees information on the fly. For our purposes we need to implement two handler interfaces so that we could perform status operations on our Working Copy: ISVNStatusHandler and ISVNEventHandler. This handler will receive status information about Working Copy items as a status client traverses the Working Copy tree:

   1 ...
   2 public class StatusHandler implements ISVNStatusHandler, ISVNEventHandler {
   3     private boolean myIsRemote;
   4     
   5     public StatusHandler( boolean isRemote ) {
   6         myIsRemote = isRemote;
   7     }
   8 
   9     public void handleStatus( SVNStatus status ) {
  10         /*
  11          * Gets  the  status  of  file/directory/symbolic link  text  contents. 
  12          * It is  SVNStatusType  who  contains  information on the state of  an 
  13          * item. 
  14          */
  15         SVNStatusType contentsStatus = status.getContentsStatus( );
  16 
  17         String pathChangeType = " ";
  18         
  19         boolean isAddedWithHistory = status.isCopied( );
  20         if ( contentsStatus == SVNStatusType.STATUS_MODIFIED ) {
  21             /*
  22              * The contents of the file have been Modified.
  23              */
  24             pathChangeType = "M";
  25         } else if ( contentsStatus == SVNStatusType.STATUS_CONFLICTED ) {
  26             /*
  27              * The item is in a state of Conflict.
  28              */
  29             pathChangeType = "C";
  30         } else if ( contentsStatus == SVNStatusType.STATUS_DELETED ) {
  31             /*
  32              * The item has been scheduled for Deletion from the repository.
  33              */
  34             pathChangeType = "D";
  35         } else if ( contentsStatus == SVNStatusType.STATUS_ADDED ) {
  36             /*
  37              * The item has been scheduled for Addition to the repository.
  38              */
  39             pathChangeType = "A";
  40         } else if ( contentsStatus == SVNStatusType.STATUS_UNVERSIONED ) {
  41             /*
  42              * The item is not  under  version control.
  43              */
  44             pathChangeType = "?";
  45         } else if ( contentsStatus == SVNStatusType.STATUS_EXTERNAL ) {
  46             /*
  47              * The item is unversioned, but is used by an eXternals definition.
  48              */
  49             pathChangeType = "X";
  50         } else if ( contentsStatus == SVNStatusType.STATUS_IGNORED ) {
  51             /*
  52              * The item is Ignored.
  53              */
  54             pathChangeType = "I";
  55         } else if ( contentsStatus == SVNStatusType.STATUS_MISSING || contentsStatus == SVNStatusType.STATUS_INCOMPLETE ) {
  56             /*
  57              * The file, directory or  symbolic  link  item  is  under  version 
  58              * control but is missing or somehow incomplete. 
  59              */
  60             pathChangeType = "!";
  61         } else if ( contentsStatus == SVNStatusType.STATUS_OBSTRUCTED ) {
  62             /*
  63              * The item is in  the  repository as one kind of object, 
  64              * but what's actually in the user's working 
  65              * copy is some other kind.
  66              */
  67             pathChangeType = "~";
  68         } else if ( contentsStatus == SVNStatusType.STATUS_REPLACED ) {
  69             /*
  70              * The item was  Replaced  in  the user's working copy; that is, 
  71              * the item was deleted,  and  a  new item with the same name 
  72              * was added (within  a  single  revision). 
  73              */
  74             pathChangeType = "R";
  75         } else if ( contentsStatus == SVNStatusType.STATUS_NONE || contentsStatus == SVNStatusType.STATUS_NORMAL ) {
  76             /*
  77              * The item was not modified (normal).
  78              */
  79             pathChangeType = " ";
  80         }
  81         
  82         /*
  83          * If SVNStatusClient.doStatus(..) is invoked with  remote = true  the 
  84          * following code finds out whether the current item has  been  changed 
  85          * in the repository   
  86          */
  87         String remoteChangeType = " ";
  88 
  89         if ( status.getRemotePropertiesStatus( ) != SVNStatusType.STATUS_NONE || status.getRemoteContentsStatus( ) != SVNStatusType.STATUS_NONE ) {
  90             /*
  91              * the local item is out of date
  92              */
  93             remoteChangeType = "*";
  94         }
  95 
  96         /*
  97          * Now getting the status of properties of an item. SVNStatusType  also 
  98          * contains information on the properties state.
  99          */
 100         SVNStatusType propertiesStatus = status.getPropertiesStatus( );
 101 
 102         /*
 103          * Default - properties are normal (unmodified).
 104          */
 105         String propertiesChangeType = " ";
 106         if ( propertiesStatus == SVNStatusType.STATUS_MODIFIED ) {
 107             /*
 108              * Properties were modified.
 109              */
 110             propertiesChangeType = "M";
 111         } else if ( propertiesStatus == SVNStatusType.STATUS_CONFLICTED ) {
 112             /*
 113              * Properties are in conflict with the repository.
 114              */
 115             propertiesChangeType = "C";
 116         }
 117 
 118         /*
 119          * Whether the item was locked in the .svn working area  (for  example, 
 120          * during a commit or maybe the previous operation was interrupted, in 
 121          * this case the lock needs to be cleaned up). 
 122          */
 123         boolean isLocked = status.isLocked( );
 124         /*
 125          * Whether the item is switched to a different URL (branch).
 126          */
 127         boolean isSwitched = status.isSwitched( );
 128         /*
 129          * If the item is a file it may be locked.
 130          */
 131         SVNLock localLock = status.getLocalLock( );
 132         /*
 133          * If  doStatus()  was  run  with  remote = true  and the item is a file, 
 134          * checks whether a remote lock presents.
 135          */
 136         SVNLock remoteLock = status.getRemoteLock( );
 137         String lockLabel = " ";
 138 
 139         if ( localLock != null ) {
 140             /*
 141              * at first suppose the file is locKed
 142              */
 143             lockLabel = "K";
 144             if ( remoteLock != null ) {
 145                 /*
 146                  * if the lock-token of the local lock differs from  the  lock-
 147                  * token of the remote lock - the lock was sTolen!
 148                  */
 149                 if ( !remoteLock.getID( ).equals( localLock.getID( ) ) ) {
 150                     lockLabel = "T";
 151                 }
 152             } else {
 153                 if ( myIsRemote ) {
 154                     /*
 155                      * the  local  lock presents but there's  no  lock  in  the
 156                      * repository - the lock was Broken. This  is  true only if 
 157                      * doStatus() was invoked with remote=true.
 158                      */
 159                     lockLabel = "B";
 160                 }
 161             }
 162         } else if ( remoteLock != null ) {
 163             /*
 164              * the file is not locally locked but locked  in  the  repository -
 165              * the lock token is in some Other working copy.
 166              */
 167             lockLabel = "O";
 168         }
 169 
 170         /*
 171          * Obtains the working revision number of the item.
 172          */
 173         long workingRevision = status.getRevision( ).getNumber( );
 174         /*
 175          * Obtains the number of the revision when the item was last changed. 
 176          */
 177         long lastChangedRevision = status.getCommittedRevision( ).getNumber( );
 178         String offset = "                                ";
 179         String[] offsets = new String[3];
 180         offsets[0] = offset.substring( 0 , 6 - String.valueOf( workingRevision ).length( ) );
 181         offsets[1] = offset.substring( 0 , 6 - String.valueOf( lastChangedRevision ).length( ) );
 182         offsets[2] = offset.substring( 0 , offset.length( ) - ( status.getAuthor( ) != null ? status.getAuthor( ).length( ) : 1 ) );
 183         /*
 184          * status is shown in the manner of the native Subversion command  line
 185          * client's command "svn status"
 186          */
 187         System.out.println( pathChangeType
 188                 + propertiesChangeType
 189                 + ( isLocked ? "L" : " " )
 190                 + ( isAddedWithHistory ? "+" : " " )
 191                 + ( isSwitched ? "S" : " " )
 192                 + lockLabel
 193                 + "  "
 194                 + remoteChangeType
 195                 + "  "
 196                 + workingRevision
 197                 + offsets[0]
 198                 + ( lastChangedRevision >= 0 ? String.valueOf( lastChangedRevision ) : "?" ) + offsets[1]
 199                 + ( status.getAuthor( ) != null ? status.getAuthor( ) : "?" )
 200                 + offsets[2] + status.getFile( ).getPath( ) );
 201     }
 202     
 203     public void handleEvent( SVNEvent event , double progress ) {
 204         SVNEventAction action = event.getAction( );
 205         /*
 206          * Print out the revision against which the status was performed.  This 
 207          * event is dispatched when the SVNStatusClient.doStatus() was  invoked 
 208          * with the flag remote set to true - that is for  a  local  status  it 
 209          * won't be dispatched.
 210          */
 211         if ( action == SVNEventAction.STATUS_COMPLETED ) {
 212             System.out.println( "Status against revision:  " + event.getRevision( ) );
 213         }
 214     
 215     }
 216 
 217     public void checkCancelled( ) throws SVNCancelException {
 218     }
 219 }

We use this handler in a status function:

   1     private static void showStatus( File wcPath , boolean isRecursive , boolean isRemote , boolean isReportAll ,
   2                                    boolean isIncludeIgnored , boolean isCollectParentExternals ) throws SVNException {
   3 
   4         ourClientManager.getStatusClient( ).doStatus( wcPath , isRecursive , isRemote , isReportAll ,
   5                                                     isIncludeIgnored , isCollectParentExternals , 
   6                                                     new StatusHandler( isRemote ) );
   7     }

For getting info on Working Copy items (like svn info command) we implement an info handler:

   1 ...
   2 public class InfoHandler implements ISVNInfoHandler {
   3     
   4     public void handleInfo( SVNInfo info ) {
   5         System.out.println( "-----------------INFO-----------------" );
   6         System.out.println( "Local Path: " + info.getFile( ).getPath( ) );
   7         System.out.println( "URL: " + info.getURL( ) );
   8 
   9         if ( info.isRemote( ) && info.getRepositoryRootURL( ) != null ) {
  10             System.out.println( "Repository Root URL: " + info.getRepositoryRootURL( ) );
  11         }
  12 
  13         if ( info.getRepositoryUUID( ) != null ) {
  14             System.out.println( "Repository UUID: " + info.getRepositoryUUID( ) );
  15         }
  16 
  17         System.out.println( "Revision: " + info.getRevision( ).getNumber( ) );
  18         System.out.println( "Node Kind: " + info.getKind( ).toString( ) );
  19 
  20         if ( !info.isRemote( ) ){
  21             System.out.println( "Schedule: " + ( info.getSchedule( ) != null ? info.getSchedule( ) : "normal" ) );
  22         }
  23 
  24         System.out.println( "Last Changed Author: " + info.getAuthor( ) );
  25         System.out.println( "Last Changed Revision: " + info.getCommittedRevision( ).getNumber( ) );
  26         System.out.println( "Last Changed Date: " + info.getCommittedDate( ) );
  27 
  28         if ( info.getPropTime( ) != null ) {
  29             System.out.println( "Properties Last Updated: " + info.getPropTime( ) );
  30         }
  31 
  32         if ( info.getKind( ) == SVNNodeKind.FILE && info.getChecksum( ) != null ) {
  33             if ( info.getTextTime( ) != null ) {
  34                 System.out.println( "Text Last Updated: " + info.getTextTime( ) );
  35             }
  36             System.out.println( "Checksum: " + info.getChecksum( ) );
  37         }
  38 
  39         if ( info.getLock( ) != null ) {
  40             if ( info.getLock( ).getID( ) != null ) {
  41                 System.out.println( "Lock Token: " + info.getLock( ).getID( ) );
  42             }
  43 
  44             System.out.println( "Lock Owner: " + info.getLock( ).getOwner( ) );
  45             System.out.println( "Lock Created: " + info.getLock( ).getCreationDate( ) );
  46 
  47             if ( info.getLock( ).getExpirationDate( ) != null ) {
  48                 System.out.println( "Lock Expires: " + info.getLock( ).getExpirationDate( ) );
  49             }
  50 
  51             if ( info.getLock( ).getComment( ) != null ) {
  52                 System.out.println( "Lock Comment: " + info.getLock( ).getComment( ) );
  53             }
  54         }
  55     }
  56 }

Which we then use in our info function:

   1     private static void showInfo( File wcPath , SVNRevision revision , boolean isRecursive ) throws SVNException {
   2         ourClientManager.getWCClient( ).doInfo( wcPath , revision , isRecursive , new InfoHandler( ) );
   3     }

We also use several hooks (event handlers) to display progress information in a console while an operation is running. We implement handlers for the following operations: update, commit, several local operations (add, delete, lock, etc.):

   1 ...
   2 public class UpdateEventHandler implements ISVNEventHandler {
   3 
   4     public void handleEvent( SVNEvent event , double progress ) {
   5         /*
   6          * Gets the current action. An action is represented by SVNEventAction.
   7          * In case of an update an  action  can  be  determined  via  comparing 
   8          * SVNEvent.getAction() and SVNEventAction.UPDATE_-like constants. 
   9          */
  10         SVNEventAction action = event.getAction( );
  11         String pathChangeType = " ";
  12         if ( action == SVNEventAction.UPDATE_ADD ) {
  13             /*
  14              * the item was added
  15              */
  16             pathChangeType = "A";
  17         } else if ( action == SVNEventAction.UPDATE_DELETE ) {
  18             /*
  19              * the item was deleted
  20              */
  21             pathChangeType = "D";
  22         } else if ( action == SVNEventAction.UPDATE_UPDATE ) {
  23             /*
  24              * Find out in details what  state the item is (after  having  been 
  25              * updated).
  26              * 
  27              * Gets  the  status  of  file/directory  item   contents.  It   is 
  28              * SVNStatusType  who contains information on the state of an item.
  29              */
  30             SVNStatusType contentsStatus = event.getContentsStatus( );
  31             if ( contentsStatus == SVNStatusType.CHANGED ) {
  32                 /*
  33                  * the  item  was  modified in the repository (got  the changes 
  34                  * from the repository
  35                  */
  36                 pathChangeType = "U";
  37             } else if ( contentsStatus == SVNStatusType.CONFLICTED ) {
  38                 /*
  39                  * The file item is in  a  state  of Conflict. That is, changes
  40                  * received from the repository during an update, overlap  with 
  41                  * local changes the user has in his working copy.
  42                  */
  43                 pathChangeType = "C";
  44             } else if ( contentsStatus == SVNStatusType.MERGED ) {
  45                 /*
  46                  * The file item was merGed (those  changes that came from  the 
  47                  * repository  did  not  overlap local changes and were  merged 
  48                  * into the file).
  49                  */
  50                 pathChangeType = "G";
  51             }
  52         } else if ( action == SVNEventAction.UPDATE_EXTERNAL ) {
  53             /*for externals definitions*/
  54             System.out.println( "Fetching external item into '" + event.getFile( ).getAbsolutePath( ) + "'" );
  55             System.out.println( "External at revision " + event.getRevision( ) );
  56             return;
  57         } else if ( action == SVNEventAction.UPDATE_COMPLETED ) {
  58             /*
  59              * Working copy update is completed. Prints out the revision.
  60              */
  61             System.out.println( "At revision " + event.getRevision( ) );
  62             return;
  63         } else if ( action == SVNEventAction.ADD ) {
  64             System.out.println( "A     " + event.getPath( ) );
  65             return;
  66         } else if ( action == SVNEventAction.DELETE ) {
  67             System.out.println( "D     " + event.getPath( ) );
  68             return;
  69         } else if ( action == SVNEventAction.LOCKED ) {
  70             System.out.println( "L     " + event.getPath( ) );
  71             return;
  72         } else if ( action == SVNEventAction.LOCK_FAILED ) {
  73             System.out.println( "failed to lock    " + event.getPath( ) );
  74             return;
  75         }
  76 
  77         /*
  78          * Status of properties of an item. SVNStatusType  also
  79          * contains information on the properties state.
  80          */
  81         SVNStatusType propertiesStatus = event.getPropertiesStatus( );
  82         String propertiesChangeType = " ";
  83         if ( propertiesStatus == SVNStatusType.CHANGED ) {
  84             /*
  85              * Properties were updated.
  86              */
  87             propertiesChangeType = "U";
  88         } else if ( propertiesStatus == SVNStatusType.CONFLICTED ) {
  89             /*
  90              * Properties are in conflict with the repository.
  91              */
  92             propertiesChangeType = "C";
  93         } else if ( propertiesStatus == SVNStatusType.MERGED ) {
  94             /*
  95              * Properties that came from the repository were  merged  with  the
  96              * local ones.
  97              */
  98             propertiesChangeType = "G";
  99         }
 100 
 101         /*
 102          * Gets the status of the lock.
 103          */
 104         String lockLabel = " ";
 105         SVNStatusType lockType = event.getLockStatus();
 106         
 107         if ( lockType == SVNStatusType.LOCK_UNLOCKED ) {
 108             /*
 109              * The lock is broken by someone.
 110              */
 111             lockLabel = "B";
 112         }
 113         
 114         System.out.println( pathChangeType + propertiesChangeType + lockLabel + "       " + event.getPath( ) );
 115     }
 116 
 117     public void checkCancelled( ) throws SVNCancelException {
 118     }
 119 }
 120 
 121 ...
 122 
 123 public class CommitEventHandler implements ISVNEventHandler {
 124 
 125     public void handleEvent( SVNEvent event , double progress ) {
 126         SVNEventAction action = event.getAction( );
 127         if ( action == SVNEventAction.COMMIT_MODIFIED ) {
 128             System.out.println( "Sending   " + event.getPath( ) );
 129         } else if ( action == SVNEventAction.COMMIT_DELETED ) {
 130             System.out.println( "Deleting   " + event.getPath( ) );
 131         } else if ( action == SVNEventAction.COMMIT_REPLACED ) {
 132             System.out.println( "Replacing   " + event.getPath( ) );
 133         } else if ( action == SVNEventAction.COMMIT_DELTA_SENT ) {
 134             System.out.println( "Transmitting file data...." );
 135         } else if ( action == SVNEventAction.COMMIT_ADDED ) {
 136             /*
 137              * Gets the MIME-type of the item.
 138              */
 139             String mimeType = event.getMimeType( );
 140             if ( SVNProperty.isBinaryMimeType( mimeType ) ) {
 141                 /*
 142                  * If the item is a binary file
 143                  */
 144                 System.out.println( "Adding  (bin)  " + event.getPath( ) );
 145             } else {
 146                 System.out.println( "Adding         " + event.getPath( ) );
 147             }
 148         }
 149 
 150     }
 151     
 152     public void checkCancelled( ) throws SVNCancelException {
 153     }
 154 }
 155 
 156 ...
 157 
 158 public class WCEventHandler implements ISVNEventHandler {
 159 
 160     public void handleEvent( SVNEvent event , double progress ) {
 161         SVNEventAction action = event.getAction( );
 162 
 163         if ( action == SVNEventAction.ADD ) {
 164             /*
 165              * The item is scheduled for addition.
 166              */
 167             System.out.println( "A     " + event.getPath( ) );
 168             return;
 169         }else if ( action == SVNEventAction.COPY ){
 170             /*
 171              * The  item  is  scheduled for addition  with history (copied,  in 
 172              * other words).
 173              */
 174             System.out.println( "A  +  " + event.getPath( ) );
 175             return;
 176         }else if ( action == SVNEventAction.DELETE ) {
 177             /*
 178              * The item is scheduled for deletion. 
 179              */
 180             System.out.println( "D     " + event.getPath( ) );
 181             return;
 182         } else if ( action == SVNEventAction.LOCKED ){
 183             /*
 184              * The item is locked.
 185              */
 186             System.out.println( "L     " + event.getPath( ) );
 187             return;
 188         } else if ( action == SVNEventAction.LOCK_FAILED ) {
 189             /*
 190              * Locking operation failed.
 191              */
 192             System.out.println( "failed to lock    " + event.getPath( ) );
 193             return;
 194         }
 195     }
 196 
 197     public void checkCancelled( ) throws SVNCancelException {
 198     }
 199 }

There are also two auxiliary functions: one for handling errors

   1     private static void error( String message , Exception e ) {
   2         System.err.println(message + ( e != null ? ": " + e.getMessage( ) : "" ) );
   3         System.exit( 1 );
   4     }

and one for creating some local dummy file/directory trees:

   1     private static final void createLocalDir( File aNewDir , File[] localFiles , String[] fileContents ) {
   2         if ( !aNewDir.mkdirs( ) ) {
   3             error( "failed to create a new directory '" + aNewDir.getAbsolutePath( ) + "'.", null );
   4         }
   5 
   6         for( int i = 0; i < localFiles.length; i++ ) {
   7             File aNewFile = localFiles[i];
   8             try {
   9                 if ( !aNewFile.createNewFile( ) ) {
  10                     error( "failed to create a new file '" + aNewFile.getAbsolutePath( ) + "'." , null );
  11                 }
  12             } catch ( IOException ioe ) {
  13                 aNewFile.delete( );
  14                 error( "error while creating a new file '" + aNewFile.getAbsolutePath( ) + "'" , ioe );
  15             }
  16         
  17             String contents = null;
  18             if ( i > fileContents.length - 1 ) {
  19                 continue;
  20             }
  21             contents = fileContents[i];
  22             
  23             /*
  24              * writing a text into the file
  25              */
  26             FileOutputStream fos = null;
  27             try {
  28                 fos = new FileOutputStream( aNewFile );
  29                 fos.write( contents.getBytes( ) );
  30             } catch ( FileNotFoundException fnfe ) {
  31                 error( "the file '" + aNewFile.getAbsolutePath( ) + "' is not found" , fnfe );
  32             } catch ( IOException ioe ) {
  33                 error( "error while writing into the file '" + aNewFile.getAbsolutePath( ) + "'" , ioe );
  34             } finally {
  35                 if ( fos != null ) {
  36                     try {
  37                         fos.close( );
  38                     } catch ( IOException ioe ) {
  39                         //
  40                     }
  41                 }
  42             }
  43         }
  44     }

And now, step by step, we demonstrate using all these functions in a main program. Firts of all, we need some initializations to be performed:

   1 ...
   2 public class WorkingCopy {
   3 
   4     private static SVNClientManager ourClientManager;
   5     private static ISVNEventHandler myCommitEventHandler;
   6     private static ISVNEventHandler myUpdateEventHandler;
   7     private static ISVNEventHandler myWCEventHandler;
   8     
   9     public static void main( String[] args ) throws SVNException {
  10         FSRepositoryFactory.setup( );
  11 
  12         SVNURL repositoryURL = null;
  13         try {
  14             repositoryURL = SVNURL.parseURIEncoded( "file://localhost/testRep" );
  15         } catch ( SVNException e ) {
  16             //
  17         }
  18 
  19         String myWorkingCopyPath = "/MyWorkingCopy";
  20 
  21         String importDir = "/importDir";
  22         String importFile = importDir + "/importFile.txt";
  23         String importFileText = "This unversioned file is imported into a repository";
  24         
  25         String newDir = "/newDir";
  26         String newFile = newDir + "/newFile.txt";
  27         String fileText = "This is a new file added to the working copy";
  28 
  29         /*
  30          * That's where a new directory will be created
  31          */
  32         SVNURL url = repositoryURL.appendPath( "MyRepos" , false );
  33         /*
  34          * That's where '/MyRepos' will be copied to (branched)
  35          */
  36         SVNURL copyURL = repositoryURL.appendPath( "MyReposCopy" , false );
  37 
  38         /*
  39          * That's where a local directory will be imported into.
  40          * Note that it's not necessary that the '/importDir' directory must already
  41          * exist - it will created if necessary. 
  42          */
  43         SVNURL importToURL = url.appendPath( importDir , false );
  44               
  45         /*
  46          * Creating custom handlers that will process events
  47          */
  48         myCommitEventHandler = new CommitEventHandler( );
  49         
  50         myUpdateEventHandler = new UpdateEventHandler( );
  51         
  52         myWCEventHandler = new WCEventHandler( );
  53         
  54         /*
  55          * Creates a default run-time configuration options driver. Default options 
  56          * created in this way use the Subversion run-time configuration area (for 
  57          * instance, on a Windows platform it can be found in the '%APPDATA%\Subversion' 
  58          * directory). 
  59          * 
  60          * readonly = true - not to save  any configuration changes that can be done 
  61          * during the program run to a config file (config settings will only 
  62          * be read to initialize; to enable changes the readonly flag should be set
  63          * to false).
  64          * 
  65          * SVNWCUtil is a utility class that creates a default options driver.
  66          */
  67         ISVNOptions options = SVNWCUtil.createDefaultOptions( true );
  68 
  69         ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager( );
  70         
  71         /*
  72          * Creates an instance of SVNClientManager providing a default auth 
  73          * manager and an options driver
  74          */
  75         ourClientManager = SVNClientManager.newInstance( options , authManager );
  76         
  77         /*
  78          * Registers a commit event handler
  79          */
  80         ourClientManager.getCommitClient( ).setEventHandler( myCommitEventHandler );
  81         
  82         /*
  83          * Registers an update event handler
  84          */
  85         ourClientManager.getUpdateClient( ).setEventHandler( myUpdateEventHandler );
  86 
  87         /*
  88          * Registers a WC event handler
  89          */
  90         ourClientManager.getWCClient( ).setEventHandler( myWCEventHandler );
  91         ...

Then (listing main actions):

1.First we make a directory in our repository

WC_Example1.png

2. Then we import a local dummy directory into it

WC_Example2.png

3. Then we

WC_Example3.png

4. Then we copy MyRepos to a new location - MyReposCopy

WC_Example4.png

5. Then we

WC_Example5.png

The same steps (marked with numbers to the left) in code:

   1         ...
   2         long committedRevision = -1;
   3         System.out.println( "Making a new directory at '" + url + "'..." );
   4         try {
   5             /*
   6              * creates a new version comtrolled directory in a repository and 
   7              * displays what revision the repository was committed to
   8              */
   9 1           committedRevision = makeDirectory(url, "making a new directory at '" + url + "'").getNewRevision();
  10         } catch( SVNException svne ) {
  11             error( "error while making a new directory at '" + url + "'" , svne );
  12         }
  13         System.out.println( "Committed to revision " + committedRevision );
  14         System.out.println( );
  15 
  16         File anImportDir = new File( importDir );
  17         File anImportFile = new File( anImportDir , SVNPathUtil.tail( importFile ) );
  18         /*
  19          * creates a new local directory - './importDir' and a new file - 
  20          * './importDir/importFile.txt' that will be imported into the repository
  21          * into the '/MyRepos/importDir' directory 
  22          */
  23         createLocalDir( anImportDir , new File[] { anImportFile } , new String[] { importFileText } );
  24         
  25         System.out.println( "Importing a new directory into '" + importToURL + "'..." );
  26         try {
  27             /*
  28              * recursively imports an unversioned directory into a repository 
  29              * and displays what revision the repository was committed to
  30              */
  31             boolean isRecursive = true;
  32 2           committedRevision = importDirectory( anImportDir , importToURL , "importing a new directory '" + anImportDir.getAbsolutePath( ) + "'" , isRecursive ).getNewRevision( );
  33         } catch( SVNException svne ) {
  34             error( "error while importing a new directory '" + anImportDir.getAbsolutePath( ) + "' into '" + importToURL + "'" , svne );
  35         }
  36         System.out.println( "Committed to revision " + committedRevision );
  37         System.out.println( );
  38         
  39         /*
  40          * creates a local directory where the working copy will be checked out into
  41          */
  42         File wcDir = new File( myWorkingCopyPath );
  43         if ( wcDir.exists() ) {
  44             error ( "the destination directory '"
  45                     + wcDir.getAbsolutePath( ) + "' already exists!" , null );
  46         }
  47         wcDir.mkdirs( );
  48 
  49         System.out.println( "Checking out a working copy from '" + url + "'..." );
  50         try {
  51             /*
  52              * recursively checks out a working copy from url into wcDir.
  53              * SVNRevision.HEAD means the latest revision to be checked out. 
  54              */
  55 3.1         checkout( url , SVNRevision.HEAD , wcDir , true );
  56         } catch ( SVNException svne ) {
  57             error( "error while checking out a working copy for the location '"
  58                             + url + "'" , svne );
  59         }
  60         System.out.println( );
  61         
  62         /*
  63          * recursively displays info for wcDir at the current working revision 
  64          * in the manner of 'svn info -R' command
  65          */
  66         try {
  67             showInfo( wcDir , SVNRevision.WORKING , true );
  68         } catch ( SVNException svne ) {
  69             error( "error while recursively getting info for the working copy at'"
  70                     + wcDir.getAbsolutePath( ) + "'" , svne );
  71         }
  72         System.out.println( );
  73 
  74         File aNewDir = new File( wcDir , newDir );
  75         File aNewFile = new File( aNewDir , SVNPathUtil.tail( newFile ) );
  76         /*
  77          * creates a new local directory - 'wcDir/newDir' and a new file - 
  78          * '/MyWorkspace/newDir/newFile.txt' 
  79          */
  80         createLocalDir( aNewDir , new File[] { aNewFile } , new String[] { fileText } );
  81         
  82         System.out.println( "Recursively scheduling a new directory '" + aNewDir.getAbsolutePath( ) + "' for addition..." );
  83         try {
  84             /*
  85              * recursively schedules aNewDir for addition
  86              */
  87 3.2         addEntry( aNewDir );
  88         } catch ( SVNException svne ) {
  89             error( "error while recursively adding the directory '"
  90                     + aNewDir.getAbsolutePath( ) + "'" , svne );
  91         }
  92         System.out.println( );
  93 
  94         boolean isRecursive = true;
  95         boolean isRemote = true;
  96         boolean isReportAll = false;
  97         boolean isIncludeIgnored = true;
  98         boolean isCollectParentExternals = false;
  99         System.out.println( "Status for '" + wcDir.getAbsolutePath( ) + "':" );
 100         try {
 101             /*
 102              * gets and shows status information for the WC directory.
 103              * status will be recursive on wcDir, will also cover the repository, 
 104              * won't cover unmodified entries, will disregard 'svn:ignore' property 
 105              * ignores (if any), will ignore externals definitions.
 106              */
 107             showStatus( wcDir , isRecursive , isRemote , isReportAll ,
 108                     isIncludeIgnored , isCollectParentExternals );
 109         } catch ( SVNException svne ) {
 110             error( "error while recursively performing status for '"
 111                     + wcDir.getAbsolutePath( ) + "'" , svne );
 112         }
 113         System.out.println( );
 114 
 115         System.out.println( "Updating '" + wcDir.getAbsolutePath( ) + "'..." );
 116         try {
 117             /*
 118              * recursively updates wcDir to the latest revision (SVNRevision.HEAD)
 119              */
 120             update( wcDir , SVNRevision.HEAD , true );
 121         } catch ( SVNException svne ) {
 122             error( "error while recursively updating the working copy at '"
 123                     + wcDir.getAbsolutePath( ) + "'" , svne );
 124         }
 125         System.out.println( "" );
 126         
 127         System.out.println( "Committing changes for '" + wcDir.getAbsolutePath( ) + "'..." );
 128         try {
 129             /*
 130              * commits changes in wcDir to the repository with not leaving items 
 131              * locked (if any) after the commit succeeds; this will add aNewDir & 
 132              * aNewFile to the repository. 
 133              */
 134 3.3         committedRevision = commit( wcDir , false , "'/newDir' with '/newDir/newFile.txt' were added" ).getNewRevision( );
 135         } catch ( SVNException svne ) {
 136             error( "error while committing changes to the working copy at '"
 137                     + wcDir.getAbsolutePath( )
 138                     + "'" , svne );
 139         }
 140         System.out.println( "Committed to revision " + committedRevision );
 141         System.out.println( );
 142 
 143         System.out
 144                 .println( "Locking (with stealing if the entry is already locked) '"
 145                         + aNewFile.getAbsolutePath( ) + "'." );
 146         try {
 147             /*
 148              * locks aNewFile with stealing (if it has been already locked by someone
 149              * else), providing a lock comment
 150              */
 151             lock( aNewFile , true , "locking '/newDir/newFile.txt'" );
 152         } catch ( SVNException svne ) {
 153             error( "error while locking the working copy file '"
 154                     + aNewFile.getAbsolutePath( ) + "'" , svne );
 155         }
 156         System.out.println( );
 157 
 158         System.out.println( "Status for '" + wcDir.getAbsolutePath( ) + "':" );
 159         try {
 160             /*
 161              * displays status once again to see the file is really locked
 162              */
 163             showStatus( wcDir , isRecursive , isRemote , isReportAll ,
 164                     isIncludeIgnored , isCollectParentExternals );
 165         } catch ( SVNException svne ) {
 166             error( "error while recursively performing status for '"
 167                     + wcDir.getAbsolutePath( ) + "'" , svne );
 168         }
 169         System.out.println( );
 170 
 171         System.out.println( "Copying '" + url + "' to '" + copyURL + "'..." );
 172         try {
 173             /*
 174              * makes a branch of url at copyURL - that is URL->URL copying
 175              * with history
 176              */
 177 4           committedRevision = copy( url , copyURL , false ,
 178                     "remotely copying '" + url + "' to '" + copyURL + "'" )
 179                     .getNewRevision( );
 180         } catch ( SVNException svne ) {
 181             error( "error while copying '" + url + "' to '"
 182                     + copyURL + "'" , svne );
 183         }
 184        /*
 185         * displays what revision the repository was committed to
 186         */
 187         System.out.println( "Committed to revision " + committedRevision );
 188         System.out.println( );
 189 
 190         System.out.println( "Switching '" + wcDir.getAbsolutePath() + "' to '"
 191                 + copyURL + "'..." );
 192         try {
 193             /*
 194              * recursively switches wcDir to copyURL in the latest revision 
 195              * (SVNRevision.HEAD)
 196              */
 197 5.1         switchToURL( wcDir , copyURL , SVNRevision.HEAD , true );
 198         } catch ( SVNException svne ) {
 199             error( "error while switching '"
 200                     + wcDir.getAbsolutePath( ) + "' to '" + copyURL + "'" , svne );
 201         }
 202         System.out.println( );
 203 
 204         /*
 205          * recursively displays info for the working copy once again to see
 206          * it was really switched to a new URL
 207          */
 208         try {
 209             showInfo( wcDir , SVNRevision.WORKING , true );
 210         } catch ( SVNException svne ) {
 211             error( "error while recursively getting info for the working copy at'"
 212                     + wcDir.getAbsolutePath( ) + "'" , svne );
 213         }
 214         System.out.println( );
 215 
 216         System.out.println( "Scheduling '" + aNewDir.getAbsolutePath( ) + "' for deletion ..." );
 217         try {
 218             /*
 219              * schedules aNewDir for deletion (with forcing)
 220              */
 221 5.2         delete( aNewDir , true );
 222         } catch ( SVNException svne ) {
 223             error( "error while schediling '"
 224                     + wcDir.getAbsolutePath( ) + "' for deletion" , svne );
 225         }
 226         System.out.println( );
 227 
 228         System.out.println( "Status for '" + wcDir.getAbsolutePath( ) + "':" );
 229         try {
 230             /*
 231              * recursively displays status once more to see whether aNewDir
 232              * was really scheduled for deletion  
 233              */
 234             showStatus( wcDir , isRecursive , isRemote , isReportAll ,
 235                     isIncludeIgnored , isCollectParentExternals );
 236         } catch ( SVNException svne ) {
 237             error( "error while recursively performing status for '"
 238                     + wcDir.getAbsolutePath( ) + "'" , svne );
 239         }
 240         System.out.println( );
 241 
 242         System.out.println( "Committing changes for '" + wcDir.getAbsolutePath( ) + "'..." );
 243         try {
 244             /*
 245              * lastly commits changes in wcDir to the repository; all items that
 246              * were locked by the user (if any) will be unlocked after the commit 
 247              * succeeds; this commit will remove aNewDir from the repository. 
 248              */
 249 5.3         committedRevision = commit(
 250                     wcDir ,
 251                     false ,
 252                     "deleting '" + aNewDir.getAbsolutePath( )
 253                             + "' from the filesystem as well as from the repository" ).getNewRevision( );
 254         } catch ( SVNException svne ) {
 255             error( "error while committing changes to the working copy '"
 256                     + wcDir.getAbsolutePath( )
 257                     + "'" , svne );
 258         }
 259         System.out.println( "Committed to revision " + committedRevision );
 260         System.exit( 0 );
 261     }

And if the program runs successfully you'll see in your console the following output:

Making a new directory at 'file:///G:/testRep/MyRepos'...
Committed to revision 5

Importing a new directory into 'file:///G:/testRep/MyRepos/importDir'...
Adding         importFile.txt
Committed to revision 6

Checking out a working copy from 'file:///G:/testRep/MyRepos'...
A         importDir
AU        importDir/importFile.txt
At revision 6

-----------------INFO-----------------
Local Path: G:\MyWorkingCopy
URL: file:///G:/testRep/MyRepos
Repository UUID: bcf16199-7f9e-be47-a3e5-e4d194b5d0ae
Revision: 6
Node Kind: dir
Schedule: normal
Last Changed Author: userName
Last Changed Revision: 6
Last Changed Date: Fri Jul 07 16:19:37 NOVST 2006
-----------------INFO-----------------
Local Path: G:\MyWorkingCopy\importDir
URL: file:///G:/testRep/MyRepos/importDir
Repository UUID: bcf16199-7f9e-be47-a3e5-e4d194b5d0ae
Revision: 6
Node Kind: dir
Schedule: normal
Last Changed Author: userName
Last Changed Revision: 6
Last Changed Date: Fri Jul 07 16:19:37 NOVST 2006
-----------------INFO-----------------
Local Path: G:\MyWorkingCopy\importDir\importFile.txt
URL: file:///G:/testRep/MyRepos/importDir/importFile.txt
Repository UUID: bcf16199-7f9e-be47-a3e5-e4d194b5d0ae
Revision: 6
Node Kind: file
Schedule: normal
Last Changed Author: userName
Last Changed Revision: 6
Last Changed Date: Fri Jul 07 16:19:37 NOVST 2006
Properties Last Updated: Fri Jul 07 16:19:38 NOVST 2006
Text Last Updated: Fri Jul 07 16:19:37 NOVST 2006
Checksum: 75e9e68e21ae4453f318424738aef57e
 
Recursively scheduling a new directory 'G:\MyWorkingCopy\newDir' for addition...
A     newDir
A     newDir/newFile.txt   
 
Status for 'G:\MyWorkingCopy':
A          0     ?    ?                               G:\MyWorkingCopy\newDir\newFile.txt
A          0     ?    ?                               G:\MyWorkingCopy\newDir
 
Updating 'G:\MyWorkingCopy'...
At revision 6 
 
Committing changes for 'G:\MyWorkingCopy'...
Adding         newDir
Adding         newDir/newFile.txt
Transmitting file data....
Committed to revision 7
 
Locking (with stealing if the entry is already locked) 'G:\MyWorkingCopy\newDir\newFile.txt'.
L     newFile.txt
 
Status for 'G:\MyWorkingCopy':
     K     7     7     userName                        G:\MyWorkingCopy\newDir\newFile.txt
 
Copying 'file:///G:/testRep/MyRepos' to 'file:///G:/testRep/MyReposCopy'...
Committed to revision 8
 
Switching 'G:\MyWorkingCopy' to 'file:///G:/testRep/MyReposCopy'...
  B       newDir/newFile.txt
At revision 8
 
-----------------INFO-----------------
Local Path: G:\MyWorkingCopy
URL: file:///G:/testRep/MyReposCopy
Repository UUID: bcf16199-7f9e-be47-a3e5-e4d194b5d0ae
Revision: 8
Node Kind: dir
Schedule: normal
Last Changed Author: userName
Last Changed Revision: 8
Last Changed Date: Fri Jul 07 16:19:42 NOVST 2006
-----------------INFO-----------------
Local Path: G:\MyWorkingCopy\importDir
URL: file:///G:/testRep/MyReposCopy/importDir
Repository UUID: bcf16199-7f9e-be47-a3e5-e4d194b5d0ae
Revision: 8
Node Kind: dir
Schedule: normal
Last Changed Author: userName
Last Changed Revision: 6
Last Changed Date: Fri Jul 07 16:19:37 NOVST 2006
-----------------INFO-----------------
Local Path: G:\MyWorkingCopy\importDir\importFile.txt
URL: file:///G:/testRep/MyReposCopy/importDir/importFile.txt
Repository UUID: bcf16199-7f9e-be47-a3e5-e4d194b5d0ae
Revision: 8
Node Kind: file
Schedule: normal
Last Changed Author: userName
Last Changed Revision: 6
Last Changed Date: Fri Jul 07 16:19:37 NOVST 2006
Properties Last Updated: Fri Jul 07 16:19:38 NOVST 2006
Text Last Updated: Fri Jul 07 16:19:37 NOVST 2006
Checksum: 75e9e68e21ae4453f318424738aef57e
-----------------INFO-----------------
Local Path: G:\MyWorkingCopy\newDir
URL: file:///G:/testRep/MyReposCopy/newDir
Repository UUID: bcf16199-7f9e-be47-a3e5-e4d194b5d0ae
Revision: 8
Node Kind: dir
Schedule: normal
Last Changed Author: userName
Last Changed Revision: 7
Last Changed Date: Fri Jul 07 16:19:41 NOVST 2006
-----------------INFO-----------------
Local Path: G:\MyWorkingCopy\newDir\newFile.txt
URL: file:///G:/testRep/MyReposCopy/newDir/newFile.txt
Repository UUID: bcf16199-7f9e-be47-a3e5-e4d194b5d0ae
Revision: 8
Node Kind: file
Schedule: normal
Last Changed Author: userName
Last Changed Revision: 7
Last Changed Date: Fri Jul 07 16:19:41 NOVST 2006
Properties Last Updated: Fri Jul 07 16:19:40 NOVST 2006
Text Last Updated: Fri Jul 07 16:19:40 NOVST 2006
Checksum: 023b67e9660b2faabaf84b10ba32c6cf 
 
Scheduling 'G:\MyWorkingCopy\newDir' for deletion ...
D     newDir/newFile.txt
D     newDir
 
Status for 'G:\MyWorkingCopy':
D          8     7     userName                        G:\MyWorkingCopy\newDir\newFile.txt
D          8     7     userName                        G:\MyWorkingCopy\newDir
 
Committing changes for 'G:\MyWorkingCopy'...
Deleting   newDir
Committed to revision 9


Download the full sources of this example program: