package org.appfuse.tool; import org.tmatesoft.svn.core.SVNCommitInfo; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory; import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl; import org.tmatesoft.svn.core.io.ISVNEditor; import org.tmatesoft.svn.core.io.ISVNReporter; import org.tmatesoft.svn.core.io.ISVNReporterBaton; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import org.tmatesoft.svn.core.io.diff.SVNDeltaProcessor; import org.tmatesoft.svn.core.io.diff.SVNDiffWindow; import org.tmatesoft.svn.core.wc.SVNWCUtil; import java.io.File; import java.io.IOException; import java.io.OutputStream; /* * This example program export contents of the repository directory into file system using * SVNKit library low level API. * * In general, approach we are using in this example is the same that is used for operations * like 'update', 'remote status', 'diff' or 'checkout'. The export operation is the most * simple one and allows to demonstrate this approach without going too much into the details. * * You may find and an article describing this (update) technique at * http://svnkit.com/kb/dev-guide-update-operation.html * * To perform any update-like operation one have to do the following: * * 1. Report the state of the client's working copy to the Subversion server. Of course, it could be * 'virtual' working copy, not necessary stored in the Subversion wc format or, in case of export or * diff operation there could be no working copy at all, which is reflected in report. * * Report is performed with the help ISVNReporter instance that is passed to the client's ISVNReporterBaton * object at the moment report have to be sent. * * 2. Process instructions received from the server. These instructions describes how to modify working copy * to make it be at the desirable revision. Amount of instructions depends on the report sent by the client. * Different operations process received instructions in different manner. For instance, update operation * updates working copy in the filsystem, remote status operation merely logs files and directories that * have to be updated and displays this information. * * With SVNKit API you may implement your own processing code, e.g. repository replication or custom merging code. * ISVNEditor is the interface which implementations process update instructions sent by the server and in * this example ISVNEditor implementation (ExportEditor) creates files and directories corresponding to those * in the repository. * */ public class SubversionUtils { private String url; private String destinationDirectory; public SubversionUtils(String url, String destinationDirectory) { /* * Initialize the library. It must be done before calling any * method of the library. */ setupLibrary(); this.url = url; this.destinationDirectory = destinationDirectory; } public void export() throws SVNException { SVNURL url = SVNURL.parseURIEncoded(this.url); String userName = "guest"; String userPassword = "guest"; /* * Prepare filesystem directory (export destination). */ File exportDir = new File(this.destinationDirectory); /*if (exportDir.exists()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Path ''{0}'' already exists", exportDir); throw new SVNException(err); } exportDir.mkdirs();*/ /* * Create an instance of SVNRepository class. This class is the main entry point * for all "low-level" Subversion operations supported by Subversion protocol. * * These operations includes browsing, update and commit operations. See * SVNRepository methods javadoc for more details. */ SVNRepository repository = SVNRepositoryFactory.create(url); /* * User's authentication information (name/password) is provided via an * ISVNAuthenticationManager instance. SVNWCUtil creates a default * authentication manager given user's name and password. * * Default authentication manager first attempts to use provided user name * and password and then falls back to the credentials stored in the * default Subversion credentials storage that is located in Subversion * configuration area. If you'd like to use provided user name and password * only you may use BasicAuthenticationManager class instead of default * authentication manager: * * authManager = new BasicAuthenticationsManager(userName, userPassword); * * You may also skip this point - anonymous access will be used. */ ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(userName, userPassword); repository.setAuthenticationManager(authManager); /* * Get type of the node located at URL we used to create SVNRepository. * * "" (empty string) is path relative to that URL, * -1 is value that may be used to specify HEAD (latest) revision. */ SVNNodeKind nodeKind = repository.checkPath("", -1); if (nodeKind == SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "No entry at URL ''{0}''", url); throw new SVNException(err); } else if (nodeKind == SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Entry at URL ''{0}'' is a file while directory was expected", url); throw new SVNException(err); } /* * Get latest repository revision. We will export repository contents at this very revision. */ long latestRevision = repository.getLatestRevision(); /* * Create reporterBaton. This class is responsible for reporting 'wc state' to the server. * * In this example it will always report that working copy is empty to receive update * instructions that are sufficient to create complete directories hierarchy and get full * files contents. */ ISVNReporterBaton reporterBaton = new ExportReporterBaton(latestRevision); /* * Create editor. This class will process update instructions received from the server and * will create directories and files accordingly. * * As we've reported 'emtpy working copy', server will only send 'addDir/addFile' instructions * and will never ask our editor implementation to modify a file or directory properties. */ ISVNEditor exportEditor = new ExportEditor(exportDir); /* * Now ask SVNKit to perform generic 'update' operation using our reporter and editor. * * We are passing: * * - revision from which we would like to export * - null as "target" name, to perform export from the URL SVNRepository was created for, * not from some child directory. * - reporterBaton * - exportEditor. */ repository.update(latestRevision, null, true, reporterBaton, exportEditor); //System.out.println("Exported revision: " + latestRevision); } /* * ReporterBaton implementation that always reports 'empty wc' state. */ private static class ExportReporterBaton implements ISVNReporterBaton { private long exportRevision; public ExportReporterBaton(long revision){ exportRevision = revision; } public void report(ISVNReporter reporter) throws SVNException { try { /* * Here empty working copy is reported. * * ISVNReporter includes methods that allows to report mixed-rev working copy * and even let server know that some files or directories are locally missing or * locked. */ reporter.setPath("", null, exportRevision, true); /* * Don't forget to finish the report! */ reporter.finishReport(); } catch (SVNException svne) { reporter.abortReport(); System.out.println("Report failed."); } } } /* * ISVNEditor implementation that will add directories and files into the target directory * accordingly to update instructions sent by the server. */ private static class ExportEditor implements ISVNEditor { private File myRootDirectory; private SVNDeltaProcessor myDeltaProcessor; /* * root - the local directory where the node tree is to be exported into. */ public ExportEditor(File root) { myRootDirectory = root; /* * Utility class that will help us to transform 'deltas' sent by the * server to the new file contents. */ myDeltaProcessor = new SVNDeltaProcessor(); } /* * Server reports revision to which application of the further * instructions will update working copy to. */ public void targetRevision(long revision) throws SVNException { } /* * Called before sending other instructions. */ public void openRoot(long revision) throws SVNException { } /* * Called when a new directory has to be added. * * For each 'addDir' call server will call 'closeDir' method after * all children of the added directory are added. * * This implementation creates corresponding directory below root directory. */ public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException { File newDir = new File(myRootDirectory, path); if (!newDir.exists()) { if (!newDir.mkdirs()) { //SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "error: failed to add the directory ''{0}''.", newDir); //throw new SVNException(err); //System.err.println(err.getMessage() + "Ignoring and not overriding."); } } //System.out.println("dir added: " + path); } /* * Called when there is an existing directory that has to be 'opened' either * to modify this directory properties or to process other files and directories * inside this directory. * * In case of export this method will never be called because we reported * that our 'working copy' is empty and so server knows that there are * no 'existing' directories. */ public void openDir(String path, long revision) throws SVNException { } /* * Instructs to change opened or added directory property. * * This method is called to update properties set by the user as well * as those created automatically, like "svn:committed-rev". * See SVNProperty class for default property names. * * When property has to be deleted value will be 'null'. */ public void changeDirProperty(String name, String value) throws SVNException { } /* * Called when a new file has to be created. * * For each 'addFile' call server will call 'closeFile' method after * sending file properties and contents. * * This implementation creates empty file below root directory, file contents * will be updated later, and for empty files may not be sent at all. */ public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { File file = new File(myRootDirectory, path); if (file.exists()) { //SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "error: exported file ''{0}'' already exists!", file); //System.err.println(err.getMessage() + "Ignoring and not overriding."); //throw new SVNException(err); } else { try { file.createNewFile(); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "error: cannot create new file ''{0}''", file); throw new SVNException(err); } } } /* * Called when there is an existing files that has to be 'opened' either * to modify file contents or properties. * * In case of export this method will never be called because we reported * that our 'working copy' is empty and so server knows that there are * no 'existing' files. */ public void openFile(String path, long revision) throws SVNException { } /* * Instructs to add, modify or delete file property. * In this example we skip this instruction, but 'real' export operation * may inspect 'svn:eol-style' or 'svn:mime-type' property values to * transfor file contents propertly after receiving. */ public void changeFileProperty(String path, String name, String value) throws SVNException { } /* * Called before sending 'delta' for a file. Delta may include instructions * on how to create a file or how to modify existing file. In this example * delta will always contain instructions on how to create a new file and so * we set up deltaProcessor with 'null' base file and target file to which we would * like to store the result of delta application. */ public void applyTextDelta(String path, String baseChecksum) throws SVNException { myDeltaProcessor.applyTextDelta(null, new File(myRootDirectory, path), false); } /* * Server sends deltas in form of 'diff windows'. Depending on the file size * there may be several diff windows. Utility class SVNDeltaProcessor processes * these windows for us. */ public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { return myDeltaProcessor.textDeltaChunk(diffWindow); } /* * Called when all diff windows (delta) is transferred. */ public void textDeltaEnd(String path) throws SVNException { myDeltaProcessor.textDeltaEnd(); } /* * Called when file update is completed. * This call always matches addFile or openFile call. */ public void closeFile(String path, String textChecksum) throws SVNException { //System.out.println("file added: " + path); } /* * Called when all child files and directories are processed. * This call always matches addDir, openDir or openRoot call. */ public void closeDir() throws SVNException { } /* * Insturcts to delete an entry in the 'working copy'. Of course will not be * called during export operation. */ public void deleteEntry(String path, long revision) throws SVNException { } /* * Called when directory at 'path' should be somehow processed, * but authenticated user (or anonymous user) doesn't have enough * access rights to get information on this directory (properties, children). */ public void absentDir(String path) throws SVNException { } /* * Called when file at 'path' should be somehow processed, * but authenticated user (or anonymous user) doesn't have enough * access rights to get information on this file (contents, properties). */ public void absentFile(String path) throws SVNException { } /* * Called when update is completed. */ public SVNCommitInfo closeEdit() throws SVNException { return null; } /* * Called when update is completed with an error or server * requests client to abort update operation. */ public void abortEdit() throws SVNException { } } /* * Initializes the library to work with a repository via * different protocols. */ private static void setupLibrary() { /* * For using over http:// and https:// */ DAVRepositoryFactory.setup(); /* * For using over svn:// and svn+xxx:// */ SVNRepositoryFactoryImpl.setup(); /* * For using over file:/// */ FSRepositoryFactory.setup(); } }