Categories
Coding

Ant Subversion Task

A pure Java Subversion task for Ant.

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 + "]");

  }
}

Leave a Reply