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.SVNStatus;
import org.tmatesoft.svn.core.SVNWorkspaceAdapter;
import org.tmatesoft.svn.core.SVNWorkspaceManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
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;
import org.tmatesoft.svn.util.SVNUtil;
/**
*
* @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;
// Show SVN command output?
private boolean verbose = false;
// Are SVN operations recursive?
private boolean recursive = true;
// SVN Commit/Add/Delete/Move message
private String message = null;
/**
*
* @param msg
*/
public void setMessage(String msg) {
message = msg;
}
/**
*
* @return
*/
public String getMessage() {
return message;
}
/**
* @return Returns the recursive flag.
*/
public boolean getRecursive() {
return recursive;
}
/**
* @param recursive
*/
public void setRecursive(boolean recursive) {
this.recursive = recursive;
}
/**
* @return Returns the verbose.
*/
public boolean getVerbose() {
return verbose;
}
/**
* @param verbose The verbose to set.
*/
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
/**
*
*/
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();
SVNRepositoryFactoryImpl.setup();
// SVN Root is not needed for every operation (e.g. update)
if(svnRoot != null) {
location = SVNRepositoryLocation.parseURL(svnRoot);
repository = SVNRepositoryFactory.create(location);
// TODO how do we handle creds for updates?
if (usingCredentials){
log("Setting credentials [" + username + "," + password + "]");
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, new Object[]{});
} 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());
if (verbose) {
workspace.addWorkspaceListener(new AntSVNWorkSpaceAdapter());
}
log("Checking out " + location.getPath() + " to "
+ dest.getAbsolutePath());
revision = workspace.checkout(location, revision, false, recursive);
log("Checked out " + location.getPath() + " [revision " + revision
+ "]");
}
/**
* Execute a svn update
* @throws SVNException
*/
public void doUpdate() throws SVNException {
ISVNWorkspace workspace = SVNUtil.createWorkspace(dest.getAbsolutePath());
final String path = SVNUtil.getWorkspacePath(workspace, dest.getAbsolutePath());
if (verbose) {
workspace.addWorkspaceListener(new AntSVNWorkSpaceAdapter());
}
log("Updating: [" + dest.getAbsolutePath() + "]");
revision = workspace.update(dest.getAbsolutePath(), revision, recursive);
log("Updated to revision " + revision);
}
/**
* Executes a svn commit
* @throws SVNException
*/
public void doCommit() throws SVNException {
final ISVNWorkspace workspace = SVNUtil.createWorkspace(dest.getAbsolutePath());
// We won't necessarily have a repository instance for commits, so we
// explicitly set the credentials on the ISVNWorkspace
if(usingCredentials)
workspace.setCredentials(username, password);
if(verbose) {
workspace.addWorkspaceListener(new AntSVNWorkSpaceAdapter());
}
log("Committing changes in: " + dest.getAbsolutePath());
log("Using credentials: " + usingCredentials);
log("Message = " + message);
log("Committing in " + dest.getAbsolutePath());
// TODO The first parameter is being misused here
long revision = workspace.commit(new String[]{""}, message, true, false);
if(revision <= 0)
log("Nothing to commit");
else
log("Committed at revision " + revision);
}
/**
* Nested class that implements logging of some SVN command output
*
*/
class AntSVNWorkSpaceAdapter extends SVNWorkspaceAdapter {
private AntSVNWorkSpaceAdapter() {
}
/**
* Update handler
*/
public void updated(String updatedPath, int contentsStatus,
int propertiesStatus, long rev) {
char contents = 'U';
char properties = ' ';
if (contentsStatus == SVNStatus.ADDED) {
contents = 'A';
} else if (contentsStatus == SVNStatus.DELETED) {
contents = 'D';
} else if (contentsStatus == SVNStatus.MERGED) {
contents = 'G';
} else if (contentsStatus == SVNStatus.CONFLICTED) {
contents = 'C';
} else if (contentsStatus == SVNStatus.NOT_MODIFIED) {
contents = ' ';
} else if (contentsStatus == SVNStatus.CORRUPTED) {
contents = 'U';
}
if (propertiesStatus == SVNStatus.UPDATED) {
properties = 'U';
} else if (propertiesStatus == SVNStatus.CONFLICTED) {
properties = 'C';
}
log(contents + "" + properties + ' ' + updatedPath);
if (contents == ' ' && properties == ' ') {
return;
}
log(contents + "" + properties + ' ' + updatedPath);
if (contentsStatus == SVNStatus.CORRUPTED) {
log("svn: Checksum error: base version of file '" + updatedPath
+ "' is corrupted and was not updated.");
}
}
/**
* Commit handler
*/
public void committed(String committedPath, int kind) {
log("Committing: " + committedPath);
String verb = "Sending ";
if (kind == SVNStatus.ADDED) {
verb = "Adding ";
// TODO we need a reference to the workspace if we want to deduce MIME-type
/*
try {
String mimeType = workspace.getPropertyValue(committedPath, SVNProperty.MIME_TYPE);
if (mimeType != null && !mimeType.startsWith("text")) {
verb += " (bin) ";
}
log("mimetype: " + mimeType);
} catch (SVNException e1) {
DebugLog.error(e1);
}
*/
} else if (kind == SVNStatus.DELETED) {
verb = "Deleting ";
} else if (kind == SVNStatus.REPLACED) {
verb = "Replacing ";
}
log(verb + committedPath);
}
}
}