Tag Archives: subversion

SvnKit E170001: Negotiate authentication failed: ‘No valid credentials provided’

In a new project, attempts to programmatically access our Subversion server using SvnKit fails with an E170001 error. But, only on one Windows 7 workstation.

After a lot of searching on web for answers finally found something that helped. I had to add system property: svnkit.http.methods=Basic,Digest,Negotiate,NTLM

So, using SvnCli, which I used to debug this, you add the property using the “-D” switch to the command line.

java -Dsvnkit.http.methods=Basic,Digest,Negotiate,NTLM -cp "SvnCli\*" org.tmatesoft.svn.cli.SVN --username *** --password *** list

I also had to add this property to the Tomcat app server.

Solution?
While this does fix the problem in this instance, since only one workstation is effected, it is probably hiding an underlying configuration setup issue.

I wonder what percentage of the nation’s GDP is spent on configuration and its issues.

Original stacktrace:

Mar 18, 2015 11:40:31 AM org.tmatesoft.svn.core.internal.util.DefaultSVNDebugLogger log
SEVERE: CLI: svn: E170001: Negotiate authentication failed: 'No valid credentials provided'
org.tmatesoft.svn.core.SVNException: svn: E170001: Negotiate authentication failed: 'No valid credentials provided'
        at org.tmatesoft.svn.cli.AbstractSVNCommandEnvironment.handleWarning(AbstractSVNCommandEnvironment.java:401)
        at org.tmatesoft.svn.cli.svn.SVNListCommand.run(SVNListCommand.java:95)
        at org.tmatesoft.svn.cli.AbstractSVNCommandEnvironment.run(AbstractSVNCommandEnvironment.java:142)
        at org.tmatesoft.svn.cli.AbstractSVNLauncher.run(AbstractSVNLauncher.java:79)
        at org.tmatesoft.svn.cli.svn.SVN.main(SVN.java:26)
        at org.tmatesoft.svn.cli.SVN.main(SVN.java:22)
svn: E170001: Negotiate authentication failed: 'No valid credentials provided'

Environment

  • Java 1.7
  • SvnKit 1.8.8
  • Tomcat 7

Links

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Use JAXB to export Subversion log to Java

In a previous blog post I obtained the subversion XML log output. Now I need to convert that into Java objects in order to provide some special reporting requirements. Below I just present the unmarshal code.

Updates
Dec 3, 2014: cloned code into a github repository

As mentioned before, by using a high-level Java API to Subversion, like SVNKit, we can generate logs and have those already unmarshaled into objects. This is the recommended approach.

Let’s continue with the “brute force” way of accessing the output XML log output.

In listing one the Unmarshal class is a utility that hides the JAXB unmarshal code. The actual use of JAXB is a few lines, but this class provides methods that accepts various sources. For example using a file path:
Log theLog = new Unmarshal().path(“src/test/resources/log.xml”);

Unmarshal.java
package com.octodecillion.svn;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.Charset;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;

import org.xml.sax.SAXException;

import com.google.common.base.Preconditions;
import com.google.common.io.CharSource;
import com.google.common.io.Resources;

/**
 * @author jbetancourt
 */
public class Unmarshal {
    /** */
    public Log path(String path) throws JAXBException, SAXException,
            IOException {
        Preconditions.checkNotNull(path, "'path' param is null");
        return url(new File(path).toURI().toURL());
    }

    /**  */
    public Log url(String url) throws JAXBException, SAXException, IOException {
        Preconditions.checkNotNull(url, "'url' param is null");
        CharSource charSrc = Resources.asCharSource(new URL(url),
                Charset.defaultCharset());
        return unmarshall(charSrc);
    }

    /**  */
    public Log url(URL url) throws JAXBException, SAXException, IOException {
        Preconditions.checkNotNull(url, "'url' param is null");
        CharSource charSrc = Resources.asCharSource(url,
                Charset.defaultCharset());
        return unmarshall(charSrc);
    }

    /** */
    public Log string(String xml) throws JAXBException, SAXException,
            IOException {
        Preconditions.checkNotNull(xml, "'xml' param is null");
        return unmarshall(CharSource.wrap(xml));
    }

    /** */
    public Log unmarshall(CharSource in) throws JAXBException, SAXException,
            IOException {
        Preconditions.checkNotNull(in, "'in' param is null");
        JAXBContext jaxbContext = JAXBContext.newInstance(Log.class);
        Log theLog = null;
        
        try(Reader reader = in.openStream()){
            StreamSource source = new StreamSource(reader);
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            JAXBElement<Log> jxbElement = unmarshaller.unmarshal(source, Log.class);
            theLog = jxbElement.getValue();
        }
        
        return theLog;
    }
}
Listing 1, Unmarshal class

The Classes tree that captures the SVN log output XML
The log is in this format:

<log>
  <logentry revision="20950">
	<author>jbetancourt</author>
	<date>2014-11-10T20:12:11.910891Z</date>
	<paths>
		<path text-mods="false" kind="file" action="D" prop-mods="false">/2014/Acme/branches/rabbit-trap/www/images/beep.png
		</path>
	</paths>
	<msg>initial commit</msg>
   </logentry>
   <logentry revision="20948">
.....
</log>

Listing 0, example log contents

To use JAXB we create annotated classes to match the log XML.

These are simple use of JAXB. I’m sure there are better approaches. Note, the getter/setters
were not put in. Does a JAXB processor need these? Can’t it just use reflection?

The toString() in these classes return a JSON marshaling of the object. This is helpful for unit testing and debug.

Log.java
package com.octodecillion.svn;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * @author j.betancourt
 */
@XmlRootElement
public class Log {
	@XmlElement(name = "logentry")
	List<LogEntry> entries;

        @Override
	public String toString() {
		StringBuilder bld = new StringBuilder();
		for(LogEntry entry : entries){
			bld.append(entry.toString());
		}
		
		return bld.toString();
	}	
	
}

Listing 2, Log class

LogEntry.java
package com.octodecillion.svn;

import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

import com.google.common.base.Joiner;

/**
 * @author j.betancourt
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
public class LogEntry {
	@XmlAttribute
	String revision;
	String author;
	String date;
	@XmlElementWrapper(name="paths")
	@XmlElement(name="path")
	List<Path>paths;
	String msg;

        // getter/setters not shown 

	@Override
	public String toString() {
        	StringBuilder bld = new StringBuilder("[");		
		bld.append(Joiner.on(",").join(this.paths)).append("]");		
		return String.format("{revision:%s,author:%s,date:%s,paths:%s,msg:%s}", this.revision,this.author,this.date,bld.toString(),this.msg);
	}
}
Listing 3, LogEntry class

Path.java
package com.octodecillion.svn;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;

/**
 * @author j.betancourt
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
public class Path {
	@XmlValue
	String value;
	@XmlAttribute(name = "text-mods")
	String textmods;
	@XmlAttribute(name="kind")
	String kind;
	
	@XmlAttribute(name="action")
	String action;
	@XmlAttribute(name="prop-mods")
	String propmods;

	@Override
	public String toString() {
	   return String.format("{value:%s,kind:%s,action:%s,textmods:%s,propmods:%s}", 
             value.replaceAll("\\n+",""),kind,action,textmods,propmods);
	}
}
Listing 4, Path class

A JUnit 4 test

package com.octodecillion.svn;

import java.io.IOException;

import javax.xml.bind.JAXBException;

import org.junit.Assert;
import org.junit.Test;
import org.xml.sax.SAXException;

/**
 * @author j.betancourt
 */
public class UnMarshallTest {

	@Test
	public final void test() throws Exception {
		Log theLog = new Unmarshal().path("src/test/resources/log.xml");
		String actual = toSingleLine(theLog.toString());
		String expected1 = "{revision:20950,author:jbetancourt,date:2014-11-10T20:12:11.910891Z,paths:[{value:/2014/Acme/branches/rabbit-trap/www/images/beep.png,kind:file,action:D,textmods:false,propmods:false}],msg:initialcommit}{revision:20948,author:jbetancourt,date:2014-11-10T19:55:58.629641Z,paths:[{value:/2014/Acme/branches/rabbit-trap/www/images/desert.png,kind:file,action:D,textmods:false,propmods:false}],msg:changedicontint}{revision:20942,author:jbetancourt,date:2014-11-10T15:30:08.770266Z,paths:[{value:/2014/Acme/branches/rabbit-trap/www/scripts/acme/traps/rocket.js,kind:file,action:M,textmods:true,propmods:false},{value:/2014/Acme/branches/rabbit-trap/www/scripts/acme/traps/sled.js,kind:file,action:M,textmods:true,propmods:false}],msg:Added'usestrict'.}{revision:20941,author:rsmith,date:2014-11-10T15:20:41.707766Z,paths:[{value:/2014/Acme/branches/rabbit-trap/www/ads/umbrella/promo.html,kind:file,action:M,textmods:true,propmods:false},{value:/2014/Acme/branches/rabbit-trap/www/ads/images/umbrella.jpg,kind:file,action:A,textmods:true,propmods:true}],msg:promotionMerge}";
		String expected = toSingleLine(expected1);
		Assert.assertEquals("Created wrong object structure", expected, actual);
	}

	/**  */
	String toSingleLine(String s) {
		String s1 = s.replaceAll("\\n+", "");
		return s1.replaceAll("\\s+", "");
	}

}

Listing 5, JUnit test

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.octodecillion</groupId>
  <artifactId>svnunmarshal</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>SvnUnMarshal</name>
  <description>Example of how to use JAXB to unmarshal svn log</description>
  <!-- 
  <build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.2</version>
            <configuration>
              <source>1.7</source>
              <target>1.7</target>
            </configuration>
        </plugin>       
    </plugins>
  </build>
   -->
  <dependencies>
    <dependency>
        <groupId>org.tmatesoft.svnkit</groupId>
        <artifactId>svnkit</artifactId>
        <version>1.8.5</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>18.0</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>xmlunit</groupId>
        <artifactId>xmlunit</artifactId>
        <version>1.5</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-exec</artifactId>
        <version>1.3</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.jmockit</groupId>
        <artifactId>jmockit</artifactId>
        <version>1.13</version>
        <scope>test</scope>
    </dependency>
  </dependencies>
</project>
Listing 6, Maven POM

Further reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Run svn command from Java using Commons exec

In a project I will need to marshal a Subversion log into Java. First step was getting Java to talk to a subversion repository. There are many options for doing this. Two of these are using a SVN API like SVNKit or invoking the command line svn executable.

Lets say you decide on the second option, invoking the svn binary, how would you do it? Easiest way is to use the Apache Commons Exec library. In listing below I use Exec and the Guava API in a Java 1.7 source level. Of course, this is not a ‘reusable’ solution, for example, the arguments to the two implemented commands are fixed. But, that is all I need right now.

Example use: new Command().log(“some/repo/somewhere”);

Expand.java
package com.octodecillion.svn;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Properties;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.PumpStreamHandler;

import com.google.common.base.Preconditions;
import com.google.common.io.Resources;

/**
 * Invoke SVN commands from Java.
 * Just log and list for now.
 * <p>
 * Requires the command line SVN executable.
 * These are specified in svn.properties file in class path.
 *
 * Note: Thread safety has not been tested.
 *
 * @author j.betancourt
 *
 */
public class Command {

    /**
     * Constructor.
     * @throws IOException
     */
    public Command() throws IOException {
        try(BufferedReader is = 
             (Resources.asCharSource(Resources.getResource("svn.properties"),
			Charset.defaultCharset())).openBufferedStream()
            ){

            Properties props = new Properties();
            props.load(is);
            commandLineClientLocation = props.getProperty("commandLineClientLocation",
				DEFAULT_SVN_PROGRAM_LOCATION);
            commandLineClientFileName = props.getProperty("commandLineClientFileName", 
				DEFAULT_SVN_EXE);
        }
    }

    /**
     * @see http://svnbook.red-bean.com/en/1.4/svn.ref.svn.c.log.html
     *
     * @param url
     * @return
     * @throws IOException
     * @throws ExecuteException
     * @since Nov 18, 2014
     */
    public String log(String url) throws ExecuteException, IOException {
        Preconditions.checkNotNull(url, "url param is null");
        CommandLine cmdLine = createCmdLine()
                .addArgument("log")
                .addArgument("--stop-on-copy")
                .addArgument("--verbose")
                .addArgument("--xml")
                .addArgument(url);

        return executeCommand(cmdLine);
    }

    /**
     * @see http://svnbook.red-bean.com/en/1.4/svn.ref.svn.c.list.html
     * @param baseURL
     * @return
     * @throws IOException
     * @throws ExecuteException
     * @since Nov 19, 2014
     */
    public String list(String url) throws ExecuteException,IOException {
        Preconditions.checkNotNull(url, "url param is null");
        CommandLine cmdLine = createCmdLine()
                .addArgument("list")
                .addArgument("--xml")
                .addArgument(url);
        return executeCommand(cmdLine);
    }

    private CommandLine createCmdLine() {
        return CommandLine.parse(commandLineClientFileName);
    }

    /**
     * Execute command line at working directory.
     * @param cmdLine
     * @return String that captured the error and standard output streams
     * @throws ExecuteException
     * @throws IOException
     * @since 2014-11-20
     */
    private String executeCommand(CommandLine cmdLine) 
		throws ExecuteException,IOException {
        DefaultExecutor exec = new DefaultExecutor();
        exec.setWorkingDirectory(new File(commandLineClientLocation));

        String str ="";
        try(ByteArrayOutputStream outputStream = 
              new ByteArrayOutputStream()){

            exec.setStreamHandler(new PumpStreamHandler(outputStream));
            exec.execute(cmdLine);
            str =  outputStream.toString();
        }

        return str;
    }

    private static final String DEFAULT_SVN_EXE = "svn.exe";
    private static final String DEFAULT_SVN_PROGRAM_LOCATION = 
		"\\Program Files\\CollabNet\\Subversion Client";

    private String commandLineClientLocation = DEFAULT_SVN_PROGRAM_LOCATION;
    private String commandLineClientFileName = DEFAULT_SVN_EXE;
}

 

Further reading

  • Guava
  • Apache Commons Exec
  • SVNkit
  • Calling SVN commands from a java program
  • Creative Commons License
    This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

    Getting “svn info” using Groovy

    In a Subversion utility I had to the get the revision number of the HEAD of the current working copy. An answer found gave a Linux command line solution. Here is my Groovy version.

    Groovy allows the ability to execute a String. This returns a Process instance, we wait for its termination, and then we query the process for the return code, the output to stderr, and to stdout.

    Below I take the output and stick it into a Properties object, then dump the entries. Of course, in practice I will be using the Properties in the solution (should have used a Map).

    def command = ['svn','info','-rHEAD']
    def proc = command.execute()
    proc.waitFor()
    
    int code = proc.exitValue()
    if(code != 0){
        println "code: '${code}'"
        println "stderr: ${proc.err.text}"
        return
    }
    
    def props = new Properties()
    
    proc.in.eachLine{
        def m = (it =~ /^(.*?):(.*)$/)
        if(m){
            def matches = m[0]
            def key = matches[1].trim()
            def value = matches[2].trim()
            props.put(key,value)
        }else{
            println 'Could not parse the output of svn info'
        }
    }
    
    println 'Properties ...'
    props.each{
        println "[${it.key}]=[${it.value}]"
    }
    

    Example output
    Note: The brackets were added to provide a visual test of output, they are not stored. Also, the content was manually changed.

    Properties ...
    [Last Changed Date]=[201.............pr 2014)]
    [Last Changed Rev]=[737]
    [Path]=[Widget]
    [Repository UUID]=[ec14......b668719305]
    [Revision]=[744]
    [Relative URL]=[^/widget/branches/skunk]
    [Repository Root]=[https://widget/svn/widget]
    [Last Changed Author]=[alfred e. newman]
    [URL]=[https://widget/svn/widget/branches/skunk]
    [Node Kind]=[directory]
    

    Example error output
    On error the output would be something like:

    code: '1'
    stderr: svn: E155007: 'C:\temp' is not a working copy
    

    Via SvnAnt?
    An alternative is to use the svnant Ant task. This task has a subcommand ‘WcVersion’. It didn’t work for me.

    Ant example is shown below, but you could of use the AntBuilder in Ant.

    <target name="svn-info" depends="" description="- svn project info">
    	<svn username="${svn.username}" password="${svn.password}">
    		<wcversion path=".." prefix="svn" />
    	</svn>		
    </target>
    

    Update
    SvnAnt’s info command can be used to get this information. See http://subclipse.tigris.org/svnant/svntask.html#info

    Links

    Creative Commons License
    This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

    “.ignore” files should not be stored in a project’s source

    Many version control systems use ignore files in a folder to indicate resources that should not be tracked , the target ignored files. These ignore files could be in any folder in the project where something must be ignored, natch.

    These ignore files are a separate concern, not germane to the purpose of the source code. Should we also have files that indicate how to compile? Compile and config concerns are localized, either in the build scripts or in other types of management system. Likewise, the version control metadata should also be separate from the source.

    This is comparable to how in the past Subversion version control system had .svn files throughout a project’s source folders. These were used as part of their vcs “bookkeeping”. Currently the “bookkeeping” was centralized into one .svn file at the project’s root folder, like found in Git. (Not an SVN expert so this is just what I see when I use SVN).

    A few that use ignore files all over the place are: Git, Mercurial, CVS, and Subversion. Yes, Subversion too, except that it hides these in this nebulous ‘properties’ objects that are not seen but can cause great pain.

    To see how to use these, see for example Git’s: gitignore(5) Manual Page

    Any issues?
    Other than be a context leak, any issues? None directly. Yet, if you search the web, there are many pleas for making automation tools and apps work around the inclusion of these files. Extra steps must be taken to remove from views in IDE or in searches. And so forth.

    What should be done?
    Ignore files or properties should be handled just like everything else. They should be localized in the version control system’s metadata folder (db or workspace).

    For example, with Git, the .git repository could have a folder called “ignores”:

    ./.git/hooks
    ./.git/ignores
    ./.git/refs
    ...
    
     

    Cons
    There are probably many reasons for the current approach: performance, simplicity, etc. WTHDIK

    Creative Commons License
    This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

    Transform SVN log diff using Java XPath

    Sometimes the output of a version control system’s tools or command options must be transformed for further use. For example, you generate a log report and must then import that into a spreadsheet or database.

    Options
    One of the options for doing this is with text based tools and scripting. Another approach is using the XML output of the version control system if available. With Subversion, XML can be used for the log and diff reports. Now you can use the various XML tools, such as XQuery, XPath, XSLT, and so forth.

    DVCS?
    Out of the box Git or Mercurial, two popular Distributed Version Control Systems (DVCS), do not output in XML as easily as SVN’s –xml option. To use XML, you have to define a ‘formatter’ or ‘pretty-print’ for them to use.

    XPath
    Below I use Java and XPath to transform the SVN output into Comma-separated values (CSV) files. One good reason for using an imperative approach is that this gives the opportunity to perform more complex transformations. A case in point is that in SVN the XML output format is only available for summary reports, and thus to create more comprehensive results or send to complex destinations, more processing would be required.

    Source
    The source is just a simple approach with minimal error handling and limited to CSV output. Originally I had two methods with internal loops that were almost identical, so I made the loop handling into a call-back that gets invoked by an Anonymous class. Kind of a simple Visitor or Strategy Pattern? Closures, as available in scripting languages like Groovy, would have been a simpler solution.

    Of course a more comprehensive approach would abstract the output format or use other methods like POI to create a spreadsheet. Note that this approach requires loading the full XML into memory. For very large logs or diffs, a streaming approach would be needed.

    Source also available here.

    Example source SVN diff and log tranform to CSV
    /**
     * 
     */
    package com.octodecillion.utils;
    
    import java.io.File;
    import java.io.FileReader;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.io.Reader;
    import java.io.Writer;
    
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.xpath.XPath;
    import javax.xml.xpath.XPathConstants;
    import javax.xml.xpath.XPathFactory;
    
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    import org.xml.sax.InputSource;
    
    /**
     * Transform SVN diff and log XML output files.
     * 
     */
    @SuppressWarnings("javadoc")
    public class SvnOutputTransform {
    
    	/** Example run */
    	public static void main(final String[] args) {
    		try {
    			SvnOutputTransform svnTransform = new SvnOutputTransform();
    
    			svnTransform.diffSummaryToCsv("data/diff-summary.xml",
    					"bin/SvnDiff.csv");
    
    			svnTransform.logSummaryToCsv("data/log-summary.xml",
    					"bin/SvnLog.csv");
    
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    
    	}
    
    	/** Call back interface */
    	interface RowProcess {
    		/** accept method of Vistor pattern */
    		public void doRows(NodeList nodeList) throws Exception;
    	}
    
    	/**
    	 * 
    	 * @param dataFilePath
    	 * @param reportFilePath
    	 * @param nodeString
    	 * @param processRows
    	 */
    	public void generateReport(final String dataFilePath,
    			final String reportFilePath, final String nodeString,
    			final RowProcess processRows) {
    
    		try {
    			NodeList nodeList = setup(dataFilePath, reportFilePath, nodeString);
    			reportOut.println(HEADER_COLUMN);
    			processRows.doRows(nodeList);
    		} catch (Exception e) {
    			throw new SvnTransformException("", e);
    		} finally {
    			finallyHandler(reportOut, fr);
    		}
    	}
    
    	/**
    	 * 
    	 * @param dataFilePath
    	 * @param reportFilePath
    	 * @param xpathString
    	 * @return
    	 * @throws Exception
    	 */
    	private NodeList setup(final String dataFilePath,
    			final String reportFilePath, final String xpathString) throws Exception {
    
    		builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    		xp = XPathFactory.newInstance().newXPath();
    		fr = new FileReader(dataFilePath);
    		dom = builder.parse(new InputSource(fr));
    		reportOut = new PrintWriter(new File(reportFilePath));
    
    		Object nodes = xp.evaluate(xpathString, dom, XPathConstants.NODESET);
    		NodeList nodeList = (NodeList) nodes;
    
    		return nodeList;
    
    	}
    
    	/**
    	 * Convert SVN generated diff summary xml file to CSV. 
    	 * 
    	 * The format of the input XML is:
    	 * <diff>
    	 *	<paths>
    	 *		<path props="none" kind="file" item="modified">
    	 *			full path of resource
    	 *		</path>
    	 *  </paths>
    	 * </diff>
    	 * 
    	 * @param dataFilePath xml file generated by svn diff --xml --summary ....
    	 * @param reportFilePath destination of CSV file
    	 * @throws SvnTransformException 
    	 */
    	public void diffSummaryToCsv(final String dataFilePath,
    			final String reportFilePath) {
    
    		preconditionCheck(dataFilePath, reportFilePath);
    		
    		generateReport(dataFilePath, reportFilePath, "//diff/paths/path",
    				new RowProcess() {
    					@SuppressWarnings("synthetic-access")
    					@Override
    					public void doRows(final NodeList nodeList) throws Exception {
    						for (int i = 0; i < nodeList.getLength(); i++) {
    							Node node = nodeList.item(i);
    							String kind = xp.evaluate(KIND_ATTR_NAME, node);
    							String item = xp.evaluate(ITEM_ATTR_NAME, node);
    							String pathEntry = xp.evaluate("text()", node);
    
    							// row
    							reportOut.println(String.format(
    									"%s,%s,%s,%s,%s,%s,%s", DIFFUSER, revision,
    									date, item, kind, pathEntry, message));
    						}
    					}
    				});
    	}
    
    	/**
    	 * Convert SVN generated log summary xml file to CSV.
    	 * 
    	 * <log>
    	 * 		<logentry revision="10879">
    	 * 			<author>T16205</author>
    	 * 			<date>2013-03-15T18:10:07.264531Z</date>
    	 * 			<paths>
    	 * 				<path kind="file" action="A">
    	 * 					/2013/Amica/branches/SOW114-LifeAPP/Test/Resources/Properties/Test/HomeQuotingFields.inix
    	 * 				</path>
    	 * 			</paths>
    	 *      </logentry>
    	 * </log>
    	 * 
    	 * @throws SvnTransformException 
    	 */
    	public void logSummaryToCsv(final String dataFilePath,
    			final String reportFilePath) {
    		preconditionCheck(dataFilePath, reportFilePath);
    
    		generateReport(dataFilePath, reportFilePath, "//log/logentry",
    				new RowProcess() {
    					@SuppressWarnings("synthetic-access")
    					@Override
    					public void doRows(final NodeList nodeList) throws Exception {
    						for (int i = 0; i < nodeList.getLength(); i++) {
    							Node node = nodeList.item(i);
    							String author = xp.evaluate(AUTHOR_NODE, node);
    							String date = xp.evaluate(DATE_NODE, node);
    							String revision = xp.evaluate("@revision", node);
    							String message = "\"" + xp.evaluate(MSG_NODE, node) + "\"";
    
    							NodeList paths = (NodeList) xp.evaluate(
    									"paths/path", node, XPathConstants.NODESET);
    
    							if (paths != null) {
    								for (int k = 0; k < paths.getLength(); k++) {
    									Node aPath = paths.item(k);
    
    									String action = xp.evaluate("@action",
    											aPath);
    									action = actionToName(action);
    
    									String filePath = xp.evaluate("text()",
    											aPath);
    
    									// row
    									reportOut.println(String.format(
    											"%s,%s,%s,%s,%s,%s", author,
    											revision, date.split("T")[0],
    											action, filePath, message));
    								}
    							}
    
    						} // end each logentry		
    					}
    				});
    
    	} // end logToCsv
    	
    	/**  */
    	private String actionToName(final String n) {
    
    		try {
    			return ACTION.valueOf(n).getName();
    		} catch (Exception e) {
    			return n;
    		}
    	}
    
    	private void finallyHandler(final Writer reportOut, final Reader fr) {
    		if (reportOut != null) {
    			try {
    				reportOut.flush();
    				reportOut.close();
    			} catch (Exception e) {
    				// 
    			}
    		}
    
    		if (fr != null) {
    			try {
    				fr.close();
    			} catch (IOException e) {
    				// 				
    			}
    		}
    	}
    
    	/**
    	 * @param dataFilePath
    	 * @param reportFilePath
    	 * @throws IllegalArgumentException
    	 */
    	private void preconditionCheck(final String dataFilePath,
    			final String reportFilePath) throws IllegalArgumentException {
    		if ((dataFilePath == null) || (reportFilePath == null)) {
    			throw new IllegalArgumentException(String.format(
    					"dataFilePath='%s',reportFilePath='%s'", dataFilePath,
    					reportFilePath));
    		}
    	}
    
    	/**
    	 * SVN action codes.
    	 * 
    	 */
    	enum ACTION {
    		A("add"), M("modify"), D("delete"), Z("z");
    
    		private final String name;
    
    		private ACTION(final String name) {
    			this.name = name;
    		}
    
    		public String getName() {
    			return name;
    		}
    	}
    
    	/** Svn transform runtime exception */
    	static public class SvnTransformException extends RuntimeException {
    		private static final long serialVersionUID = 1L;
    
    		/**  */
    		public SvnTransformException(final String message, final Throwable cause) {
    			super(message, cause);
    		}
    	}
    
    	private static final String HEADER_COLUMN = "Dev,Revision,Date,Action,Kind,Path";
    	private static final String ITEM_ATTR_NAME = "@item";
    	private static final String KIND_ATTR_NAME = "@kind";
    	private static final String MSG_NODE = "msg";
    	private static final String DATE_NODE = "date";
    	private static final String AUTHOR_NODE = "author";
    	private PrintWriter reportOut;
    	private FileReader fr;
    	private static final String DIFFUSER = "DIFF";
    	private final String revision = "";
    	private final String date = "";
    	private final String message = "";
    	private DocumentBuilder builder;
    	private XPath xp;
    	private Document dom;
    
    }
    

     

    Updates

    Links

    Creative Commons License
    This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

    New Subversion version will centralize working copy metadata

    Subversion version 1.7 removes that gruesome use of those very numerous .svn folders in your working copy. CVS also used that approach, just named .cvs.

    This is more like how many ‘newer’ version control systems (VCS), like Git or Mercurial, lay out a repository.

    More info: Working Copy Metadata Storage Improvements (client)

    Creative Commons License
    This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.