Getting SVN: E200030 There are unfinished transaction while running update using svnkit

Here is the code i am using to do update using svnkit UpdateClient doUpdate()
		   setupLibrary();
	   System.out.println("---Starting SVN Update at location "+directoryLocation);
	   File dstPath = new File(directoryLocation);  	 
	   SVNClientManager clientManager = SVNClientManager.newInstance();		   
	   SVNUpdateClient uc = clientManager.getUpdateClient(); 
	   uc.setIgnoreExternals(false);
	   DefaultSVNOptions options = (DefaultSVNOptions) uc.getOptions();
	   ConflictResolverHandler crh = new ConflictResolverHandler();
	   options.setConflictHandler(crh);
	   UpdateEventHandler myUpdateEventHandler = new UpdateEventHandler();
	   uc.setEventHandler(myUpdateEventHandler);
	   //uc.doUpdate(dstPath, SVNRevision.HEAD, SVNDepth.INFINITY, false, true);
	   long[] l =  uc.doUpdate(new File[]{dstPath}, SVNRevision.HEAD,SVNDepth.INFINITY, true,true);
	   List<SVNUpdateInfoBean> getInfoList = myUpdateEventHandler.getInfoList();
	   for(SVNUpdateInfoBean infoObj:getInfoList){
		System.out.println(infoObj.getChangeType()+"--------------"+infoObj.getFile());   
	   }
	   System.out.println("COmpleted");

This happens when i commit from a working copy using eclipse to this repository(protocol of repo is file://), and trying to take update using my java code at another working copy, please let me know if i am doing anyting wrong here

Hello,
from the first glance the code looks ok except 2 things:

  • the SVNClientManager-based API is obsolete, I would recommend you to use SvnOperationFactory#createUpdate instead;
  • all the classes that have dispose() method, should be disposed using “try {} finally {}” block (both SvnOperationFactory and SVNClientManager are such classes, this is important because they keep connection pool inside; otherwise the open connections will remain).

I would recommend you to have a look at SVNKit’ own tests for the examples: https://svn.svnkit.com/repos/svnkit/trunk/svnkit/src/test/java/org/tmatesoft/svn/test/UpdateTest.java

how to use SvnOperationFactory#createUpdate.

tried what you suggested and changed my code to this:

	public static void update(String directoryLocation) throws SVNException {
	
	final SvnOperationFactory svnOperationFactory = new SvnOperationFactory();
	svnOperationFactory.setOptions(new DefaultSVNOptions()); 
	try {
		setupLibrary();
		File dstPath = new File("/Users/shahrukh/Documents/svnTest/svn2/SpotterWebSocketProject"); 			
		 final SvnUpdate update = svnOperationFactory.createUpdate();
		    update.setSingleTarget(SvnTarget.fromFile(dstPath));
		    update.setRevision(SVNRevision.HEAD);
		       update.setDepth(SVNDepth.INFINITY);
		       update.run();   
		    
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}finally{
		svnOperationFactory.dispose();
	}
     
}

Still getting same exception and repository goes to locked state, here is svn status result from console:

  L     .

L src
L src/main
L src/main/java
L src/main/java/com
L src/main/resources
L src/main/resources/static
L src/test
L src/test/java
L src/test/java/com
L src/test/java/com/
~ L S src/test/java/com/newFile3.java

also the new incoming files which are coming from update are showing status “S”, as you can see in console result “newFile3” is the new File that is added after update but is having status “S” against it.

On further checking observed that this issue is happening only when there are new files committed to repository, and when those new files are coming in working copy with the update, here is complete stack trace of exception:

org.tmatesoft.svn.core.SVNException: svn: E200030: There are unfinished transactions detected in '/Users/shahrukh/Documents/svnTest/svn2/SpotterWebSocketProject'
at org.tmatesoft.svn.core.internal.wc.SVNErrorManager.error(SVNErrorManager.java:70)
at org.tmatesoft.svn.core.internal.wc.SVNErrorManager.error(SVNErrorManager.java:57)
at org.tmatesoft.svn.core.internal.wc17.db.SVNWCDbRoot.ensureNoUnfinishedTransactions(SVNWCDbRoot.java:177)
at org.tmatesoft.svn.core.internal.wc17.db.SVNWCDb.ensureNoUnfinishedTransactions(SVNWCDb.java:212)
at org.tmatesoft.svn.core.internal.wc17.SVNWCContext.ensureNoUnfinishedTransactions(SVNWCContext.java:5185)
at org.tmatesoft.svn.core.wc2.SvnOperationFactory.releaseWcContext(SvnOperationFactory.java:1275)
at org.tmatesoft.svn.core.wc2.SvnOperationFactory.run(SvnOperationFactory.java:1255)
at org.tmatesoft.svn.core.wc2.SvnOperation.run(SvnOperation.java:294)
at com.securonix.svnpluggin.utilities.SvnUtilities.update(SvnUtilities.java:124)
at com.securonix.svnpluggin.main.Main.main(Main.java:26)

I wonder what is inside setupLibrary() and why do you call it there, in the middle of the process?
The code looks OK, HEAD and INFINITY are default values, so even they can be omitted.

svnOperationFactory.setOptions(new DefaultSVNOptions()); 

call is also redundant.

Regarding “unfinished transactions”, this usually happens when reusing classes that cannot be reused. So I think it would be useful for you to read this article: http://vcs.atspace.co.uk/2012/09/21/are-svnkit-methods-reenterable/

But in your case setupLibrary() is a black box for me, maybe it’s a cause of all evil. Other than that the code looks fine.

    public static void setupLibrary() {
    /*
     * For using over http:// and https://
     */
    DAVRepositoryFactory.setup();
    /*
     * For using over svn:// and svn+xxx://
     */
    SVNRepositoryFactoryImpl.setup();

    /*
     * For using over file:///
     */
    FSRepositoryFactory.setup();
}

setUpLibrary() is just initiating the factoryMethods, added source

With the latest SVNKit version this is not needed anymore as this initialization is embedded into SVNRepositoryFactory class and it is loaded anyway.

So your code reduces to

	final SvnOperationFactory svnOperationFactory = new SvnOperationFactory();
	try {
		File dstPath = new File("/Users/shahrukh/Documents/svnTest/svn2/SpotterWebSocketProject"); 			
		 final SvnUpdate update = svnOperationFactory.createUpdate();
		 update.setSingleTarget(SvnTarget.fromFile(dstPath));
		 update.run();   
	} finally {
		svnOperationFactory.dispose();
	}

and this is correct code assuming you’ve specified a path to the SVN working copy correctly and the working copy is not corrupted.

Could you run a test: checkout some SVN repository using ‘svn checkout’ command, update it to some old revision using ‘svn update -rOLD_REV’ and then run the code above on that working copy. Does that work?

If not, do you have a possibility to look at the code in the debugger, why exactly the error happens? The article I’ve sent to you describes that in detail.

tried with above code, it works fine if i take update of a file which exists in the working copy, the problem remains same if there is a new file to be added in working copy as part of the Update, example: if i commit a new file from another working copy, and then try to take update on second working using above code i get that exception, and on checking “svn status” of the working copy show me new file as follows:
~ L S 74 src/test/java/com/securonix/newFile6.java

and the repository gets locked

Could you trace the problem in the debugger? I’ve sent and article to you and it says:

SvnOperationFactory, that encapsulates SVNWCContext, that encapsulates SVNWCDb, that contains

private Map<String, SVNWCDbDir> dirData;

It finally contains the ‘openCount’ field and it doesn’t become zero at the end of some transaction and the goal is to understand why.

Unfortunately I cannot reproduce the problem, so I can’t do that myself. I hope that article will help you.

If you could write a minimal test reproducing the problem, I could have a look at it.

Since i couldn’t figure out the root cause of the issue, i created a new project and kept only my update operation code that i was using initially, and strangely it started working for me, here is my working code for refrence:

public static List<SVNUpdateInfoBean> update(String directoryLocation) throws SVNException {
	 SVNClientManager clientManager = SVNClientManager.newInstance();
	 final List<SVNUpdateInfoBean> updateInfoList = new ArrayList<SVNUpdateInfoBean>();
	try {
	
	   System.out.println("---Starting SVN Update at location "+directoryLocation);
	   File dstPath = new File(directoryLocation);  	 
	  	   
	   SVNUpdateClient uc = clientManager.getUpdateClient(); 
	   uc.setIgnoreExternals(false);
	   DefaultSVNOptions options = (DefaultSVNOptions) uc.getOptions();
//	   ConflictResolverHandler crh = new ConflictResolverHandler();
	  
	   options.setConflictHandler(new ISVNConflictHandler() {
		
		public SVNConflictResult handleConflict(
				SVNConflictDescription conflictDescription) throws SVNException {

			SVNConflictReason reason = conflictDescription.getConflictReason();
			SVNMergeFileSet mergeFiles = conflictDescription.getMergeFiles();
			SVNConflictChoice choice = SVNConflictChoice.THEIRS_FULL;
			System.out.println("Conflict detected and overrided by changes from repository");
			return new SVNConflictResult(choice, mergeFiles.getResultFile());

		}
	});
//	   UpdateEventHandler myUpdateEventHandler = new UpdateEventHandler();
	   uc.setEventHandler(new ISVNEventHandler() {
		
		public void checkCancelled() throws SVNCancelException {
			// TODO Auto-generated method stub
			
		}			
		public void handleEvent(SVNEvent event, double progress)
				throws SVNException {

	        /*
	         * Gets the current action. An action is represented by SVNEventAction.
	         * In case of an update an  action  can  be  determined  via  comparing 
	         * SVNEvent.getAction() and SVNEventAction.UPDATE_-like constants. 
	         */
			SVNUpdateInfoBean infoBean = new SVNUpdateInfoBean();
	        SVNEventAction action = event.getAction();
	      
	        String pathChangeType = " ";
	        if (action == SVNEventAction.UPDATE_ADD) {
	            /*
	             * the item was added
	             */
	            pathChangeType = "A";
	        } else if (action == SVNEventAction.UPDATE_DELETE) {
	            /*
	             * the item was deleted
	             */
	            pathChangeType = "D";
	        } else if (action == SVNEventAction.UPDATE_UPDATE) {
	            /*
	             * Find out in details what  state the item is (after  having  been 
	             * updated).
	             * 
	             * Gets  the  status  of  file/directory  item   contents.  It   is 
	             * SVNStatusType  who contains information on the state of an item.
	             */
	            SVNStatusType contentsStatus = event.getContentsStatus();
	            if (contentsStatus == SVNStatusType.CHANGED) {
	                /*
	                 * the  item  was  modified in the repository (got  the changes 
	                 * from the repository
	                 */
	                pathChangeType = "U";
	            }else if (contentsStatus == SVNStatusType.CONFLICTED) {
	                /*
	                 * The file item is in  a  state  of Conflict. That is, changes
	                 * received from the repository during an update, overlap  with 
	                 * local changes the user has in his working copy.
	                 */
	                pathChangeType = "C";
	            } else if (contentsStatus == SVNStatusType.MERGED) {
	                /*
	                 * The file item was merGed (those  changes that came from  the 
	                 * repository  did  not  overlap local changes and were  merged 
	                 * into the file).
	                 */
	                pathChangeType = "G";
	            }
	        } else if (action == SVNEventAction.UPDATE_EXTERNAL) {
	            /*for externals definitions*/
	            System.out.println("Fetching external item into '"
	                    + event.getFile().getAbsolutePath() + "'");
	            System.out.println("External at revision " + event.getRevision());
	            return;
	        } else if (action == SVNEventAction.UPDATE_COMPLETED) {
	            /*
	             * Updating the working copy is completed. Prints out the revision.
	             */
	            System.out.println("At revision " + event.getRevision());
	            return;
	        } else if (action == SVNEventAction.ADD){
	            System.out.println("A     " + event.getFile());
	            return;
	        } else if (action == SVNEventAction.DELETE){
	            System.out.println("D     " + event.getFile());
	            return;
	        } else if (action == SVNEventAction.LOCKED){
	            System.out.println("L     " + event.getFile());
	            return;
	        } else if (action == SVNEventAction.LOCK_FAILED){
	            System.out.println("failed to lock    " + event.getFile());
	            return;
	        }

	        /*
	         * Now getting the status of properties of an item. SVNStatusType  also
	         * contains information on the properties state.
	         */
	        SVNStatusType propertiesStatus = event.getPropertiesStatus();
	        /*
	         * At first consider properties are normal (unchanged).
	         */
	        String propertiesChangeType = " ";
	        if (propertiesStatus == SVNStatusType.CHANGED) {
	            /*
	             * Properties were updated.
	             */
	            propertiesChangeType = "U";
	        } else if (propertiesStatus == SVNStatusType.CONFLICTED) {
	            /*
	             * Properties are in conflict with the repository.
	             */
	            propertiesChangeType = "C";
	        } else if (propertiesStatus == SVNStatusType.MERGED) {
	            /*
	             * Properties that came from the repository were  merged  with  the
	             * local ones.
	             */
	            propertiesChangeType = "G";
	        }

	        /*
	         * Gets the status of the lock.
	         */
	        String lockLabel = " ";
	        SVNStatusType lockType = event.getLockStatus();
	        
	        if (lockType == SVNStatusType.LOCK_UNLOCKED) {
	            /*
	             * The lock is broken by someone.
	             */
	            lockLabel = "B";
	        }
	        
	        infoBean = new SVNUpdateInfoBean(); 
	        infoBean.setChangeType(pathChangeType);
	        infoBean.setFile(event.getFile().toString());
	        if(infoBean.getChangeType()!=null && infoBean.getChangeType()!=" "){
	        	updateInfoList.add(infoBean);
	        }
	       			
		}
	});
	   //uc.doUpdate(dstPath, SVNRevision.HEAD, SVNDepth.INFINITY, false, true);
	   long[] l =  uc.doUpdate(new File[]{dstPath}, SVNRevision.HEAD,SVNDepth.INFINITY, true,true);
	   System.out.println("COmpleted");
	   		   
} catch (Exception e) {
	
	e.printStackTrace();
}finally{
	clientManager.dispose();
}
	return updateInfoList;

}

SVNUpdateInfoBean is a bean that contains info about files modified and their statuses, but still don’t know what was wrong in the previous code.
Thanks for the help and providing relevant references.

I’m glad that you have a piece of code that works for you. If you look at SVNKit code, you’ll see that

SVNUpdateClient

is actually implemented using a newer SvnOperationFactory-based interface and SVNClientManager#dispose disposes its internal SvnOperationFactory as well.

So if you have motivation to switch to the newer API, you can replace doUpdate(…) with its SvnOperationFactory-based implementation using minimal steps preserving code operability. But if you don’t, you can leave everything as is. Both old and new APIs are both backward compatible and interchangeable, the only difference is that new features sometimes come to the new API only (but sometimes to both APIs as well when not causing combinatorial explosion of the methods arguments). But recently, we don’t update SVNKit much, so this shouldnt’t make big difference.