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/
*
* <svn commmand="checkout" username="foo" password="bar" svnRoot="http://localhost/svn/sample/trunk"
* dest="/usr/sandbox/sample" revision="1" />
*
*/
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 + "]");
}
}