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