Sandbox.java
001 /*
002  *  Sandbox.java
003  *  Copyright (c) 1998-2008, The University of Sheffield.
004  *
005  *  This code is from the GATE project (http://gate.ac.uk/) and is free
006  *  software licenced under the GNU General Public License version 3. It is
007  *  distributed without any warranty. For more details see COPYING.txt in the
008  *  top level directory (or at http://gatewiki.sf.net/COPYING.txt).
009  *
010  *  Hamish Cunningham 8th January 2007
011  *
012  *  Based on example code from SVNKit in the
013  *    org.tmatesoft.svn.examples.wc
014  *  package. Thanks TMate Software Ltd.
015  */
016 package gate.versioning.svnkit;
017 
018 import java.io.File;
019 import java.util.concurrent.locks.ReentrantLock;
020 
021 import org.apache.log4j.Logger;
022 import org.tmatesoft.svn.core.SVNCommitInfo;
023 import org.tmatesoft.svn.core.SVNDepth;
024 import org.tmatesoft.svn.core.SVNException;
025 import org.tmatesoft.svn.core.SVNCancelException;
026 import org.tmatesoft.svn.core.SVNURL;
027 import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
028 import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
029 import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
030 import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
031 import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
032 import org.tmatesoft.svn.core.wc.ISVNOptions;
033 import org.tmatesoft.svn.core.wc.SVNClientManager;
034 import org.tmatesoft.svn.core.wc.SVNConflictChoice;
035 import org.tmatesoft.svn.core.wc.SVNCopySource;
036 import org.tmatesoft.svn.core.wc.SVNRevision;
037 import org.tmatesoft.svn.core.wc.SVNStatus;
038 import org.tmatesoft.svn.core.wc.SVNUpdateClient;
039 import org.tmatesoft.svn.core.wc.SVNWCUtil;
040 
041 /**
042  * This class is a thin layer over the SVNKit working copy API. All methods that
043  * trigger write operations on the sandbox tree are synchronized, but to ensure
044  * that there is not more than one Sandbox associated with a particular
045  * directory tree on disk the {@link SandboxManager} class should be used.
046  */
047 public class Sandbox {
048   /** Logger. */
049   static Logger lgr = Logger.getLogger(Sandbox.class);
050 
051   /** Client manager. */
052   SVNClientManager clientManager;
053 
054   /** SVN event handler. */
055   CommitEventHandler commitEventHandler = new CommitEventHandler(this);
056 
057   /** SVN event handler. */
058   UpdateEventHandler updateEventHandler = new UpdateEventHandler(this);
059 
060   /** SVN event handler. */
061   SandboxEventHandler sandboxEventHandler = new SandboxEventHandler(this);
062 
063   /** Flag to indicate that the SVNKit library has been initialised. */
064   static boolean svnkitIsInitialised = false;
065 
066   /**
067    * Flag to indicate whether this sandbox has been closed.  If so, all future
068    * requests will throw an exception.
069    */
070   private volatile boolean closed = false;
071 
072   /**
073    * Mutex used to synchronize access to methods of this class in a more
074    * controllable way than simple synchronization.
075    */
076   private ReentrantLock mutex = new ReentrantLock(true);
077 
078   /**
079    * Flag to indicate whether the currently running operation (if any) should
080    * be allowed to be cancelled before it has completed normally.  Generally
081    * speaking, background update operations such as regular wiki updates in CoW
082    * would be pre-emptible, but smaller operations such as those arising from a
083    * wiki editing session would not be pre-emptible.  <i>Implementation
084    * note:</i> The value of this field should only be modified by a thread that
085    * holds the {@link #mutex}.
086    */
087   private boolean currentOperationPreemptible = false;
088 
089   /** Construction. Not appropriate if authentication is needed. */
090   public Sandbox() {
091     this(""""null);
092   // Sandbox()
093 
094   /** Construction. */
095   public Sandbox(String name, String password) {
096     this(name,password,null);
097   }
098   
099   public Sandbox(String name, String password, File svnconfig) {
100     Sandbox.init();
101 
102     /*
103      * Create a default run-time configuration options driver using the the
104      * Subversion run-time configuration area.
105      */
106     ISVNOptions options = SVNWCUtil.createDefaultOptions(svnconfig,true);
107 
108     /*
109      * Creates an instance of SVNClientManager providing authentication
110      * information (name, password) and an options driver
111      */
112     clientManager =
113       SVNClientManager.newInstance((DefaultSVNOptions)options, name, password);
114 
115     initEventHandlers();
116   // Sandbox(String, String)
117 
118   /** Construction with a custom authentication manager. */
119   public Sandbox(ISVNAuthenticationManager authManager) {
120     this(authManager,null);
121   }
122   
123   public Sandbox(ISVNAuthenticationManager authManager, File svnconfig) {
124     Sandbox.init();
125 
126     /*
127      * Create a default run-time configuration options driver using the the
128      * Subversion run-time configuration area.
129      */
130     ISVNOptions options = SVNWCUtil.createDefaultOptions(svnconfig,true);
131 
132     /*
133      * Creates an instance of SVNClientManager providing authentication
134      * information (name, password) and an options driver
135      */
136     clientManager = SVNClientManager.newInstance(options, authManager);
137 
138     initEventHandlers();
139   // Sandbox(String, String)
140 
141   private void initEventHandlers() {
142     /* Sets a custom event handler for an SVNCommitClient instance */
143     clientManager.getCommitClient().setEventHandler(commitEventHandler);
144 
145     /* Sets a custom event handler for an SVNUpdateClient instance */
146     clientManager.getUpdateClient().setEventHandler(updateEventHandler);
147 
148     /* Sets a custom event handler for an SVNWCClient instance */
149     clientManager.getWCClient().setEventHandler(sandboxEventHandler);
150   }
151 
152   /**
153    * Initializes SVNKit to work with an SVN repository via various protocols.
154    */
155   synchronized private static void init() {
156     if(svnkitIsInitialisedreturn;
157 
158     /* For using over http:// and https:// */
159     DAVRepositoryFactory.setup();
160 
161     /* For using over svn:// and svn+xxx:// */
162     SVNRepositoryFactoryImpl.setup();
163 
164     /* For using over file:/// */
165     FSRepositoryFactory.setup();
166 
167     svnkitIsInitialised = true;
168   // init()
169 
170   /**
171    * Cancel any running preemptible operation, lock the mutex, reset the cancel
172    * flag and mark the current operation as pre-emptible or not according to
173    * the specified parameter.
174    */
175   private final void lock(boolean preemptible) {
176     // the following line may block if there is a running operation.  If the
177     // running operation is preemptible then it should be cancelled shortly
178     mutex.lock();
179     currentOperationPreemptible = preemptible;
180   }
181 
182   /**
183    * Unlock the mutex.
184    */
185   private final void unlock() {
186     mutex.unlock();
187   }
188 
189 
190   /**
191    * Close this sandbox, preventing any future operations.  This method locks
192    * the mutex before setting the close flag, so will wait for any currently
193    * running operation to complete before returning.
194    */
195   public void close() {
196     lock(false);
197     try {
198       closed = true;
199       clientManager.dispose();
200     }
201     finally {
202       unlock();
203     }
204   }
205 
206   /**
207    * Creates a new version controlled directory (doesn't create any intermediate
208    * directories) right in a repository. Like 'svn mkdir URL -m "some comment"'
209    * command. It's done by invoking
210    
211    * SVNCommitClient.doMkDir(SVNURL[] urls, String commitMessage)
212    
213    * which takes the following parameters:
214    
215    * urls - an array of URLs that are to be created;
216    
217    * commitMessage - a commit log message since a URL-based directory creation
218    * is immediately committed to a repository.
219    
220    @return SVNCommitInfo containing information on the new revision committed
221    *         (revision number, etc.)
222    */
223   public SVNCommitInfo makeDirectory(SVNURL url,
224     String commitMessagethrows SVNException {
225     lock(false);
226     try {
227       if(closedthrow new IllegalStateException("Sandbox closed");
228       return clientManager.getCommitClient().doMkDir(new SVNURL[]{url},
229         commitMessage);
230     }
231     finally {
232       unlock();
233     }
234   }
235 
236   /**
237    * Imports an unversioned directory into a repository location denoted by a
238    * destination URL (all necessary parent non-existent paths will be created
239    * automatically). This operation commits the repository to a new revision.
240    * Like 'svn import PATH URL (-N) -m "some comment"' command. It's done by
241    * invoking
242    
243    * SVNCommitClient.doImport(File path, SVNURL dstURL, String commitMessage,
244    * boolean recursive)
245    
246    * which takes the following parameters:
247    
248    * path - a local unversioned directory or singal file that will be imported
249    * into a repository;
250    
251    * dstURL - a repository location where the local unversioned directory/file
252    * will be imported into; this URL path may contain non-existent parent paths
253    * that will be created by the repository server;
254    
255    * commitMessage - a commit log message since the new directory/file are
256    * immediately created in the repository;
257    
258    * recursive - if true and path parameter corresponds to a directory then the
259    * directory will be added with all its child subdirictories, otherwise the
260    * operation will cover only the directory itself (only those files which are
261    * located in the directory).
262    
263    @return SVNCommitInfo containing information on the new revision committed
264    *         (revision number, etc.).
265    */
266   public SVNCommitInfo importDirectory(File localPath,
267     SVNURL dstURL, String commitMessage, boolean isRecursive)
268     throws SVNException {
269     lock(false);
270     try {
271       if(closedthrow new IllegalStateException("Sandbox closed");
272       return clientManager.getCommitClient().doImport(localPath, dstURL,
273         commitMessage, null, true, false, SVNDepth.fromRecurse(isRecursive));
274     }
275     finally {
276       unlock();
277     }
278   // importDirectory
279 
280   /**
281    * Committs changes in a working copy to a repository. Like 'svn commit PATH
282    * -m "some comment"' command. It's done by invoking
283    
284    * SVNCommitClient.doCommit(File[] paths, boolean keepLocks, String
285    * commitMessage, boolean force, boolean recursive)
286    
287    * which takes the following parameters:
288    
289    * paths - working copy paths which changes are to be committed;
290    
291    * keepLocks - if true then doCommit(..) won't unlock locked paths; otherwise
292    * they will be unlocked after a successful commit;
293    
294    * commitMessage - a commit log message;
295    
296    * force - if true then a non-recursive commit will be forced anyway;
297    
298    * recursive - if true and a path corresponds to a directory then doCommit(..)
299    * recursively commits changes for the entire directory, otherwise - only for
300    * child entries of the directory;
301    
302    @return SVNCommitInfo containing information on the new revision committed
303    *         (revision number, etc.).
304    */
305   public SVNCommitInfo commit(File[] wcPath, boolean keepLocks,
306     String commitMessagethrows SVNException {
307     lock(false);
308     try {
309       if(closedthrow new IllegalStateException("Sandbox closed");
310       return clientManager.getCommitClient().doCommit(wcPath, keepLocks,
311         commitMessage, null, null, false, false, SVNDepth.fromRecurse(true));
312     }
313     finally {
314       unlock();
315     }
316   // commit
317 
318   /**
319    * Checks out a working copy from a repository. Like 'svn checkout URL[@REV]
320    * PATH (-r..)' command; It's done by invoking
321    
322    * SVNUpdateClient.doCheckout(SVNURL url, File dstPath, SVNRevision
323    * pegRevision, SVNRevision revision, SVNDepth depth, boolean
324    * allowUnversionedObstructions)
325    
326    * which takes the following parameters:
327    
328    * url - a repository location from where a working copy is to be checked out;
329    
330    * dstPath - a local path where the working copy will be fetched into;
331    
332    * pegRevision - an SVNRevision representing a revision to concretize url
333    * (what exactly URL a user means and is sure of being the URL he needs); in
334    * other words that is the revision in which the URL is first looked up;
335    
336    * revision - a revision at which a working copy being checked out is to be;an
337    
338    * depth - whether to check out the whole tree rooted at that node, just the
339    * directory with no contents, just the directory and its immediate children,
340    * etc. We pass depth INFINITY if recursive == true and depth EMPTY otherwise.
341    
342    * allowUnversionedObstructions - how to handle the case when there is already
343    * a file in the directory into which we are checking out that has the same
344    * name as something from the repository. We pass false, which means the
345    * checkout fails in this case.
346    
347    @return long the number of the revision the sandbox is at
348    */
349   public long checkout(SVNURL url, SVNRevision revision,
350     File destPath, boolean isRecursivethrows SVNException {
351     lock(false);
352     try {
353       if(closedthrow new IllegalStateException("Sandbox closed");
354 
355       SVNUpdateClient updateClient = clientManager.getUpdateClient();
356 
357       /* sets externals not to be ignored during the checkout */
358       updateClient.setIgnoreExternals(false);
359 
360       return updateClient.doCheckout(url, destPath, revision, revision,
361         isRecursive ? SVNDepth.INFINITY : SVNDepth.EMPTY, false);
362     }
363     finally {
364       unlock();
365     }
366   // checkout
367 
368   /**
369    * Updates a working copy in a non-preemptible operation.
370    *
371    @see #update(File,SVNRevision,boolean,boolean)
372    */
373   public long update(File wcPath, SVNRevision updateToRevision,
374     boolean isRecursivethrows SVNException {
375     return update(wcPath, updateToRevision, isRecursive, false);
376   }
377 
378   /**
379    * Updates a working copy (brings changes from the repository into the working
380    * copy). Like 'svn update PATH' command; It's done by invoking
381    
382    * SVNUpdateClient.doUpdate(File file, SVNRevision revision, boolean
383    * recursive)
384    
385    * which takes the following parameters:
386    
387    * file - a working copy entry that is to be updated;
388    
389    * revision - a revision to which a working copy is to be updated;
390    
391    * recursive - if true and an entry is a directory then doUpdate(..)
392    * recursively updates the entire directory, otherwise - only child entries of
393    * the directory.
394    
395    @param preemptible whether this update can be pre-empted by a "more
396    * important" update on the same sandbox.  Typically this would be set to
397    * true for background update jobs that update a whole sandbox and false for
398    * interactive jobs that just update particular files.
399    @return long the number of the revision the sandbox was updated to
400    */
401   public long update(File wcPath, SVNRevision updateToRevision,
402       boolean isRecursive, boolean preemptiblethrows SVNException {
403     lock(preemptible);
404     try {
405       if(closedthrow new IllegalStateException("Sandbox closed");
406       // pro-actively check for cancellation so we get out of the way if
407       // someone else is waiting.
408       checkCancelled();
409       SVNUpdateClient updateClient = clientManager.getUpdateClient();
410 
411       /* sets externals not to be ignored during the update */
412       updateClient.setIgnoreExternals(false);
413 
414       return updateClient.doUpdate(wcPath, updateToRevision, SVNDepth
415         .fromRecurse(isRecursive), false, false);
416     }
417     finally {
418       unlock();
419     }
420   // update
421 
422   /**
423    * Updates a working copy to a different URL. Like 'svn switch URL' command.
424    * It's done by invoking
425    
426    * SVNUpdateClient.doSwitch(File file, SVNURL url, SVNRevision revision,
427    * boolean recursive)
428    
429    * which takes the following parameters:
430    
431    * file - a working copy entry that is to be switched to a new url;
432    
433    * url - a target URL a working copy is to be updated against;
434    
435    * revision - a revision to which a working copy is to be updated;
436    
437    * recursive - if true and an entry (file) is a directory then doSwitch(..)
438    * recursively switches the entire directory, otherwise - only child entries
439    * of the directory.
440    
441    @return long the number of the revision the sandbox was updated to
442    */
443   public long switchToURL(File wcPath, SVNURL url,
444     SVNRevision updateToRevision, boolean isRecursivethrows SVNException {
445     lock(false);
446     try {
447       if(closedthrow new IllegalStateException("Sandbox closed");
448 
449       SVNUpdateClient updateClient = clientManager.getUpdateClient();
450 
451       /* sets externals not to be ignored during the switch */
452       updateClient.setIgnoreExternals(false);
453 
454       return updateClient.doSwitch(wcPath, url, SVNRevision.UNDEFINED,
455         updateToRevision, SVNDepth.fromRecurse(isRecursive), false, false);
456     }
457     finally {
458       unlock();
459     }
460   // switchToURL
461 
462   /**
463    * Updates a working copy in the same was as 'svn switch --relocate'
464    **/
465   public void relocate(File wcPath, SVNURL oldURL, SVNURL newURL)
466     throws SVNException {
467     lock(false);
468     try {
469       if(closedthrow new IllegalStateException("Sandbox closed");
470 
471       SVNUpdateClient updateClient = clientManager.getUpdateClient();
472 
473       /* sets externals not to be ignored during the switch */
474       updateClient.setIgnoreExternals(false);
475 
476       updateClient.doRelocate(wcPath, oldURL, newURL, false);
477     }
478     finally {
479       unlock();
480     }
481   }
482 
483   /**
484    * Displays status information on local path(s). Like 'svn status (-u) (-N)'
485    * command. It's done by invoking
486    
487    * SVNStatusClient.doStatus(File path, boolean recursive, boolean remote,
488    * boolean reportAll, boolean includeIgnored, boolean collectParentExternals,
489    * ISVNStatusHandler handler)
490    
491    * which takes the following parameters:
492    
493    * path - an entry which status info to be gathered;
494    
495    * recursive - if true and an entry is a directory then doStatus(..) collects
496    * status info not only for that directory but for each item inside stepping
497    * down recursively;
498    
499    * remote - if true then doStatus(..) will cover the repository (not only the
500    * working copy) as well to find out what entries are out of date;
501    
502    * reportAll - if true then doStatus(..) will also include unmodified entries;
503    
504    * includeIgnored - if true then doStatus(..) will also include entries being
505    * ignored;
506    
507    * collectParentExternals - if true then externals definitions won't be
508    * ignored;
509    
510    * handler - an implementation of ISVNStatusHandler to process status info per
511    * each entry doStatus(..) traverses; such info is collected in an SVNStatus
512    * object and is passed to a handler's handleStatus(SVNStatus status) method
513    * where an implementor decides what to do with it.
514    */
515   public void showStatus(File wcPath, boolean isRecursive, boolean isRemote,
516     boolean isReportAll, boolean isIncludeIgnored,
517     boolean isCollectParentExternalsthrows SVNException {
518     lock(true);
519     try {
520       /*
521        * StatusHandler displays status information for each entry in the console
522        * (in the manner of the native Subversion command line client)
523        */
524       clientManager.getStatusClient().doStatus(wcPath, SVNRevision.HEAD,
525         SVNDepth.fromRecurse(isRecursive), isRemote, isReportAll,
526         isIncludeIgnored, isCollectParentExternals, new StatusHandler(isRemote),
527         null);
528     }
529     finally {
530       unlock();
531     }
532   // showStatus
533 
534   /**
535    * Collects status information on local path(s). It's done by invoking
536    
537    * SVNStatusClient.doStatus(File path, boolean remote) which takes the
538    * following parameters:
539    
540    * path - an entry which status info to be gathered;
541    
542    * remote - if true then doStatus(..) will cover the repository (not only the
543    * working copy) as well to find out what entries are out of date;
544    */
545   public SVNStatus doStatus(File wcPath, boolean isRemotethrows SVNException {
546     lock(false);
547     try {
548       return clientManager.getStatusClient().doStatus(wcPath, isRemote);
549     }
550     finally {
551       unlock();
552     }
553   // doStatus
554 
555   /**
556    * Resolve a conflict on local path(s). It's done by invoking
557    
558    * SVNStatusClient.doResolve(File path, boolean recursive) which takes the
559    * following parameters:
560    
561    * path - an entry which status info to be gathered;
562    
563    * recursive - if true then doResolve(..) will cover the repository (stepping
564    * down recursively)
565    */
566   public void doResolve(File wcPath, boolean isRecursivethrows SVNException {
567     lock(false);
568     try {
569       if(closedthrow new IllegalStateException("Sandbox closed");
570       clientManager.getWCClient().doResolve(wcPath,
571         SVNDepth.fromRecurse(isRecursive), SVNConflictChoice.MERGED);
572     }
573     finally {
574       unlock();
575     }
576   // doResolve
577 
578   /**
579    * Revert uncommitted changes. It's done by invoking
580    
581    * SVNStatusClient.doRevert(File[] wcPaths, SVNDepth depth,
582    *                          Collection changelists) which takes the
583    * following parameters:
584    
585    * wcPaths - paths of the entries to revert
586    
587    * depth - we pass SVNDepth.INFINITY, which means that if any of the wcPaths
588    * are directories (as opposed to normal files) their contents will be
589    * reverted recursively.
590    *
591    * changelists - a collection of String changelist names - we pass null,
592    * which means changelists are ignored.
593    */
594   public void revert(File... wcPathsthrows SVNException {
595     lock(false);
596     try {
597       if(closedthrow new IllegalStateException("Sandbox closed");
598       clientManager.getWCClient().doRevert(wcPaths, SVNDepth.INFINITY, null);
599     }
600     finally {
601       unlock();
602     }
603   // revert
604 
605   /**
606    * Collects information on local path(s). Like 'svn info (-R)' command. It's
607    * done by invoking
608    
609    * SVNWCClient.doInfo(File path, SVNRevision revision, boolean recursive,
610    * ISVNInfoHandler handler)
611    
612    * which takes the following parameters:
613    
614    * path - a local entry for which info will be collected;
615    
616    * revision - a revision of an entry which info is interested in; if it's not
617    * WORKING then info is got from a repository;
618    
619    * recursive - if true and an entry is a directory then doInfo(..) collects
620    * info not only for that directory but for each item inside stepping down
621    * recursively;
622    
623    * handler - an implementation of ISVNInfoHandler to process info per each
624    * entry doInfo(..) traverses; such info is collected in an SVNInfo object and
625    * is passed to a handler's handleInfo(SVNInfo info) method where an
626    * implementor decides what to do with it.
627    */
628   public void showInfo(File wcPath, SVNRevision revision, boolean isRecursive)
629     throws SVNException {
630     lock(true);
631     try {
632       /*
633        * InfoHandler displays information for each entry in the console (in the
634        * manner of the native Subversion command line client)
635        */
636       clientManager.getWCClient().doInfo(wcPath, SVNRevision.UNDEFINED, revision,
637         SVNDepth.fromRecurse(isRecursive), null, new InfoHandler());
638     }
639     finally {
640       unlock();
641     }
642   // showInfo
643 
644   /**
645    * Puts directories and files under version control scheduling them for
646    * addition to a repository. They will be added in a next commit. Like 'svn
647    * add PATH' command. It's done by invoking
648    
649    * SVNWCClient.doAdd(File path, boolean force, boolean mkdir, boolean
650    * climbUnversionedParents, boolean recursive)
651    
652    * which takes the following parameters:
653    
654    * path - an entry to be scheduled for addition;
655    
656    * force - set to true to force an addition of an entry anyway;
657    
658    * mkdir - if true doAdd(..) creates an empty directory at path and schedules
659    * it for addition, like 'svn mkdir PATH' command;
660    
661    * climbUnversionedParents - if true and the parent of the entry to be
662    * scheduled for addition is not under version control, then doAdd(..)
663    * automatically schedules the parent for addition, too;
664    
665    * recursive - if true and an entry is a directory then doAdd(..) recursively
666    * schedules all its inner dir entries for addition as well.
667    */
668   public void addEntry(File wcPaththrows SVNException {
669     addEntry(wcPath, true);
670   // addEntry
671 
672   /**
673    * Puts directories and files under version control scheduling them for
674    * addition to a repository. They will be added in a next commit. Like 'svn
675    * add PATH' command.
676    *
677    @param wcPath the file or directory to add.  Its parent must already be
678    *         known to subversion (though not necessarily committed).
679    @param recurse if true, and wcPath names a directory, recursively add all
680    *         the directories contents.  If false, just add the directory itself
681    *         but not its contents.  Has no effect if wcPath names a normal
682    *         file.  Note this is different from the recurse parameter to most
683    *         other methods in this class, where non-recursive includes files in
684    *         the directory but not subdirectories.
685    */
686   public void addEntry(File wcPath, boolean recursethrows SVNException {
687     lock(false);
688     try {
689       if(closedthrow new IllegalStateException("Sandbox closed");
690       clientManager.getWCClient().doAdd(wcPath, false, false, false,
691         (recurse ? SVNDepth.INFINITY : SVNDepth.EMPTY), false, false);
692     }
693     finally {
694       unlock();
695     }
696   // addEntry
697 
698   /**
699    * Locks working copy paths, so that no other user can commit changes to them.
700    * Like 'svn lock PATH' command. It's done by invoking
701    
702    * SVNWCClient.doLock(File[] paths, boolean stealLock, String lockMessage)
703    
704    * which takes the following parameters:
705    
706    * paths - an array of local entries to be locked;
707    
708    * stealLock - set to true to steal the lock from another user or working
709    * copy;
710    
711    * lockMessage - an optional lock comment string.
712    */
713   public void lock(File wcPath, boolean isStealLock,
714     String lockCommentthrows SVNException {
715     lock(false);
716     try {
717       if(closedthrow new IllegalStateException("Sandbox closed");
718       clientManager.getWCClient().doLock(new File[]{wcPath}, isStealLock,
719         lockComment);
720     }
721     finally {
722       unlock();
723     }
724   // lock
725 
726   /**
727    * Schedules directories and files for deletion from version control upon the
728    * next commit (locally). Like 'svn delete PATH' command. It's done by
729    * invoking
730    
731    * SVNWCClient.doDelete(File path, boolean force, boolean dryRun)
732    
733    * which takes the following parameters:
734    
735    * path - an entry to be scheduled for deletion;
736    
737    * force - a boolean flag which is set to true to force a deletion even if an
738    * entry has local modifications;
739    
740    * dryRun - set to true not to delete an entry but to check if it can be
741    * deleted; if false - then it's a deletion itself.
742    */
743   public void delete(File wcPath, boolean force)
744     throws SVNException {
745     lock(false);
746     try {
747       if(closedthrow new IllegalStateException("Sandbox closed");
748       clientManager.getWCClient().doDelete(wcPath, force, false);
749     }
750     finally {
751       unlock();
752     }
753   // delete
754 
755   /**
756    * Duplicates srcURL to dstURL (URL to URL) preserving history. Like 'svn copy
757    * srcURL dstURL -m "some comment"' command. It's done by invoking
758    
759    * doCopy(SVNURL srcURL, SVNRevision srcRevision, SVNURL dstURL, boolean
760    * isMove, String commitMessage)
761    
762    * which takes the following parameters:
763    
764    * srcURL - a source URL that is to be copied;
765    
766    * srcRevision - a definite revision of srcURL
767    
768    * dstURL - a URL where srcURL will be copied; if srcURL and dstURL are both
769    * directories then there are two cases: a) dstURL already exists - then
770    * doCopy(..) will duplicate the entire source directory and put it inside
771    * dstURL (for example, consider srcURL = svn://localhost/rep/MyRepos, dstURL
772    * = svn://localhost/rep/MyReposCopy, in this case if doCopy(..) succeeds
773    * MyRepos will be in MyReposCopy - svn://localhost/rep/MyReposCopy/MyRepos);
774    * b) dstURL doesn't exist yet - then doCopy(..) will create a directory and
775    * recursively copy entries from srcURL into dstURL (for example, consider the
776    * same srcURL = svn://localhost/rep/MyRepos, dstURL =
777    * svn://localhost/rep/MyReposCopy, in this case if doCopy(..) succeeds
778    * MyRepos entries will be in MyReposCopy, like:
779    * svn://localhost/rep/MyRepos/Dir1 ....
780    * svn://localhost/rep/MyReposCopy/Dir1...);
781    
782    * isMove - if false then srcURL is only copied to dstURL what corresponds to
783    * 'svn copy srcURL dstURL -m "some comment"'; but if it's true then srcURL
784    * will be copied and deleted - 'svn move srcURL dstURL -m "some comment"';
785    
786    * commitMessage - a commit log message since URL/URL copying is immediately
787    * committed to a repository.
788    
789    @return SVNCommitInfo containing information on the new revision.
790    */
791   public SVNCommitInfo copy(SVNURL srcURL, SVNURL dstURL,
792     boolean isMove, String commitMessagethrows SVNException {
793     lock(false);
794     try {
795       if(closedthrow new IllegalStateException("Sandbox closed");
796 
797       // introduced to support SVNKit 1.2.0
798       SVNCopySource source =
799         new SVNCopySource(SVNRevision.UNDEFINED, SVNRevision.HEAD, srcURL);
800 
801       return clientManager.getCopyClient().doCopy(new SVNCopySource[]{source},
802         dstURL, isMove, false, true, commitMessage, null);
803     }
804     finally {
805       unlock();
806     }
807   // copy
808 
809   /**
810    * Returns the URL corresponding to the repository location of the given
811    * working copy file.
812    
813    @param file
814    *          the file, which must be under version control in a sandbox. The
815    *          file need not be checked in but it must be known to subversion
816    *          (e.g. it could be added but not yet committed).
817    @return the repository URL
818    */
819   public SVNURL getRepositoryURL(File filethrows SVNException {
820     lock(false);
821     try {
822       return clientManager.getWCClient().doInfo(file, SVNRevision.WORKING)
823         .getURL();
824     }
825     finally {
826       unlock();
827     }
828   }
829 
830   /**
831    * Returns the URL of the root of the repository containing the given working
832    * copy file.
833    
834    @param file
835    *          the file, which must be under version control in a sandbox. The
836    *          file need not be checked in but it must be known to subversion
837    *          (e.g. it could be added but not yet committed).
838    @return the repository root URL
839    */
840   public SVNURL getRepositoryRoot(File filethrows SVNException {
841     lock(false);
842     try {
843       return clientManager.getWCClient().getReposRoot(file,
844           getRepositoryURL(file), SVNRevision.WORKING, null, null);
845     }
846     finally {
847       unlock();
848     }
849   }
850 
851 
852   /**
853    * If the current operation is preemptible and any other operations are
854    * waiting, throw an SVNCancelException.
855    */
856   public void checkCancelled() throws SVNCancelException {
857     if(currentOperationPreemptible && mutex.hasQueuedThreads()) {
858       throw new SVNCancelException();
859     }
860   }
861 // Sandbox