Categories
Coding

Ant Subversion Task, Part 2

I have an updated version of the Ant Subversion task that supports commits. Note to self: look at the DAV protocol spec to find out more about what is actually going on under the hood. The one snag I hit is that you won’t always have a valid Repository instance (e.g. when committing), as the SVN root is read from the administrative files, so you need to handle the credential passing explicitly for this case. Here’s a sample build.xml showing what works now:

<?xml version=”1.0″?>

<project name=”svn” default=”main” basedir=”.”>

<property name=”foo” value=”bar”/>

<target name=”main”>
<taskdef name=”svn” classname=”uk.co.researchkitchen.javasvn.ant.SvnTask” />
<delete dir=”c:/temp/sample” failonerror=”false”/>

<svn command=”checkout” svnRoot=”http://localhost/svn/sample/trunk” dest=”c:/temp/sample” revision=”10″ verbose=”true”/>

<svn command=”update” dest=”c:/temp/sample” verbose=”true”/>

<svn command=”commit” dest=”c:/temp/sample” verbose=”true” message=”hello ${foo}”/>

</target>
</project>

The code is here: SvnTask.java

I must say, I’m really impressed with the JavaSVN library – it looks pretty powerful.

Categories
Coding

Ant Subversion Task

One thing that Ant lacks is a built-in Subversion task. I am using Subversion now for almost everything (work + Open Source stuff), and find the lack of a built-in task a bit of a pain. With this in mind, I decided to make a first cut at writing an Ant task for Subversion based on the JavaSVN library. So far, it seems to be coming along well. For a sample build.xml:

<target name=”main”>
<taskdef name=”svn”
classname=”uk.co.researchkitchen.javasvn.ant.SvnTask” />
<delete dir=”c:/temp/sample” failonerror=”false”/>
<svn command=”checkout” svnRoot=”http://localhost/svn/sample/trunk” dest=”c:/temp/sample” revision=”10″/>
</target>

It will produce:

Buildfile: C:\sandbox\javasvn-src-0.8.7.2\svn-ant.xml
main:
[delete] Deleting directory C:\temp\sample
[svn] Checking out /svn/sample/trunk to C:\temp\sample
[svn] Checked out /svn/sample/trunk [revision 10]
BUILD SUCCESSFUL
Total time: 9 seconds

The code is here, if you’re interested (or have any suggestions). I’m not sure about the Reflection-based method delegation mechanism, I may replace this. This was built with Java 5.0, but using an IDE (Eclipse 3.0) that doesnt support Tiger semantics fully yet.

package uk.co.researchkitchen.javasvn.ant;

import java.io.File;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.tmatesoft.svn.core.ISVNWorkspace;
import org.tmatesoft.svn.core.SVNWorkspaceManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.ws.fs.FSEntryFactory;
import org.tmatesoft.svn.core.io.SVNException;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.io.SVNRepositoryLocation;
import org.tmatesoft.svn.core.io.SVNSimpleCredentialsProvider;

/**
 * 
 * @author rwinston
 *
 * Ant task that encapsulates some simple Subversion functionality (e.g. checkout) in pure Java, using the
 * JavaSVN libraries from http://tmate.org/svn/
 * 
 * &lt;svn commmand="checkout" username="foo" password="bar" svnRoot="http://localhost/svn/sample/trunk" 
 * dest="/usr/sandbox/sample" revision="1" /&gt;
 * 
 */
public class SvnTask extends Task {

  // The URL of the project we are checking out (e.g. http://localhost/svn/sample/trunk)
  private String svnRoot = null;

  // The destination directory for a checkout
  private File dest = null;

  // The revision to check out
  private long revision = ISVNWorkspace.HEAD;

  // The default command to execute
  private static final String DEFAULT_COMMAND = "checkout";

  // The SVN command to execute
  private String command = null;

  // Credentials
  private String username = null;

  private String password = null;

  boolean usingCredentials = false;

  // The SVN repository location
  private SVNRepositoryLocation location = null;

  // The repository representation
  private SVNRepository repository = null;

  /**
   * 
   */
  public SvnTask() {
    setTaskName("svn");
  }

  /**
   * @return Returns the password.
   */
  public String getPassword() {
    return password;
  }

  /**
   * @param password The password to set.
   */
  public void setPassword(String password) {
    this.password = password;
  }

  /**
   * @return Returns the username.
   */
  public String getUsername() {
    return username;
  }

  /**
   * @param username The username to set.
   */
  public void setUsername(String username) {
    this.username = username;
  }

  /**
   * @return Returns the command.
   */
  public String getCommand() {
    return command;
  }

  /**
   * @param command The command to set.
   */
  public void setCommand(String command) {
    this.command = command;
  }

  public long getRevision() {
    return revision;
  }

  public void setRevision(long revision) {
    this.revision = revision;
  }

  /**
   * @return Returns the dest.
   */
  public File getDest() {
    return dest;
  }

  /**
   * @param dest The dest to set.
   */
  public void setDest(File dest) {
    this.dest = dest;
  }

  /**
   * @return Returns the svnRoot.
   */
  public String getSvnRoot() {
    return svnRoot;
  }

  /**
   * @param svnRoot The svnRoot to set.
   */
  public void setSvnRoot(String svnRoot) {
    this.svnRoot = svnRoot;
  }

  /**
   * Set up and execute the relevant SVN command 
   *
   */
  public void execute() throws BuildException {

    if (command == null || command.equals("")) {
      command = DEFAULT_COMMAND;
    }

    if (username != null && password == null) {
      log("Password cannot be null if username is defined!");
      return;
    } else {
      usingCredentials = true;
    }

    try {
      // Initialize the DAV and local FS factories
      DAVRepositoryFactory.setup();
      FSEntryFactory.setup();

      location = SVNRepositoryLocation.parseURL(svnRoot);
      repository = SVNRepositoryFactory.create(location);

      if (usingCredentials)
        repository.setCredentialsProvider(new SVNSimpleCredentialsProvider(username, password));

      if (dest == null) {
        dest = getProject().getBaseDir();
      }

      if (!dest.exists()) {
        dest.mkdirs();
      }

      // Execute the relevant SVN command  
      dispatchCommand(command);

    } catch (SVNException e) {
      throw new BuildException(e, getLocation());
    }
  }

  /**
   * Dispatch to a named method based on a command name (or abbreviation)
   * @param command
   * @throws BuildException
   */
  private void dispatchCommand(String command) throws BuildException {

    final HashMap commands = new HashMap();

    // Map of commands and short names ( See org.tmatesoft.svn.cli.SVNCommand for details)
    commands.put(new String[] { "status", "st", "stat" }, "doStatus");
    commands.put(new String[] { "import" }, "doImport");
    commands.put(new String[] { "checkout", "co" },  "doCheckout");
    commands.put(new String[] { "add" }, "doAdd");
    commands.put(new String[] { "commit", "ci" }, "doCommit");
    commands.put(new String[] { "update", "up" }, "doUpdate");
    commands.put(new String[] { "delete", "rm", "remove", "del" }, "doDelete");
    commands.put(new String[] { "move", "mv", "rename", "ren" }, "doMove");
    commands.put(new String[] { "copy", "cp" }, "doCopy");
    commands.put(new String[] { "revert" }, "doRevert");
    commands.put(new String[] { "mkdir" }, "doMkdir");
    commands.put(new String[] { "propset", "pset", "ps" }, "doPropSet");
    commands.put(new String[] { "propget", "pget", "pg" }, "doPropGet");
    commands.put(new String[] { "proplist", "plist", "pl" },   "doPropList");
    commands.put(new String[] { "info" }, "doInfo");
    commands.put(new String[] { "resolved" }, "doResolved");
    commands.put(new String[] { "cat" }, "doCat");
    commands.put(new String[] { "ls" }, "doLs");
    commands.put(new String[] { "log" }, "doLog");
    commands.put(new String[] { "switch", "sw" }, "doSwitch");

    String methName = null;

    // Search the command map
        for (Iterator keys = commands.keySet().iterator(); keys.hasNext();) {
            String[] names = (String[]) keys.next();
            for (int i = 0; i < names.length; i++) {
                if (command.equals(names[i])) {
                    methName = (String) commands.get(names);
                    break;
                }
            }
            if (methName != null) {
                break;
            }
        }
        if (methName == null) {
            log("Command name " + command + " not recognized");
            throw new BuildException();
        }

        // Now locate and execute the appropriate method
        Method[] methods = this.getClass().getMethods();
        Method theMethod = null;

        for(int mIndex = 0; mIndex < methods.length; ++mIndex) {
          if(methods[mIndex].getName().equals(methName)) {
            theMethod = methods[mIndex];
            break;
          }
        }

        if(theMethod == null) {
          log("Cannot find a method called " + methName + " for SVN command " + command);
          throw new BuildException();
        }

        try {
      theMethod.invoke(this, null);
    }  catch (Exception e) {
      throw new BuildException(e, getLocation());
    }

  }


  /**
   * Execute a svn checkout
   * @throws SVNException
   *
   */
  public void doCheckout() throws SVNException {
    ISVNWorkspace workspace = SVNWorkspaceManager.createWorkspace("file", dest.getAbsolutePath());

    /*
     * Execute this when e.g. verbose mode is set 
     workspace.addWorkspaceListener(new SVNWorkspaceAdapter() {
      public void updated(String updatedPath, int contentsStatus, int propertiesStatus, long rev) {
        if ("".equals(updatedPath)) {
          return;
        }
        log("A  " + updatedPath);
      }
    });
    
    */

    log("Checking out " + location.getPath() + " to " + dest.getAbsolutePath());
    revision = workspace.checkout(location, revision, false, true);
    log("Checked out " + location.getPath() + " [revision " + revision + "]");

  }
}
Categories
Coding

XSLTC and JDK 5.0

If you’ve started using JDK 5.0 and are doing XSL transformations via Ant (e.g. for things like JUnit or Checkstyle reports), you may have problems. I had problems that manifested themselves like this:

cruisecontrol-2.2.1\work\checkout\sample\trunk\build.xml:239: javax.xml.transform.TransformerException: java.lang.RuntimeException: Unrecognized XSLTC extension 'org.apache.xalan.xslt.extensions.Redirect:write'

Which is of course because Java 5 ships with the XSLTC compiler. The easy fix is to change any instances of
xmlns:redirect="org.apache.xalan.xslt.extensions.Redirect
to
xmlns:redirect="http://xml.apache.org/xalan/redirect"