### Eclipse Workspace Patch 1.0
#P hudson-core
Index: src/main/resources/hudson/scm/SubversionSCM/config.jelly
===================================================================
RCS file: /cvs/hudson/hudson/main/core/src/main/resources/hudson/scm/SubversionSCM/config.jelly,v
retrieving revision 1.4
diff -u -r1.4 config.jelly
--- src/main/resources/hudson/scm/SubversionSCM/config.jelly	13 Feb 2007 17:13:50 -0000	1.4
+++ src/main/resources/hudson/scm/SubversionSCM/config.jelly	19 Mar 2007 09:06:31 -0000
@@ -1,17 +1,32 @@
 <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
-  <f:entry title="Modules"
-    description="
-      URL of SVN module. Multiple URLs can be specified.
+  <f:entry title="Subversion Modules"                                                               
+    description="URL of SVN module. Multiple URLs can be specified.
       If some of the URLs need username/password to access, please enter them
-      from &lt;a href='${rootURL}/scm/SubversionSCM/enterCredential'&gt;here&lt;/a&gt;.
-    ">
-    <f:textbox name="svn_modules" value="${scm.modules}"
-               checkUrl="'${rootURL}/scm/SubversionSCM/authenticationCheck?value='+encode(this.value)" />
-  </f:entry>
-  <f:entry title="Use update"
-    description="
-      If checked, Hudson will use 'svn update' whenever possible, making the build faster.
-      But this causes the artifacts from the previous build to remain when a new build starts.">
-    <f:checkbox name="svn_use_update" checked="${scm.useUpdate}"/>
-  </f:entry>
-</j:jelly>
\ No newline at end of file
+      from &lt;a href='${rootURL}/scm/SubversionSCM/enterCredential'&gt;here&lt;/a&gt;.">           
+    <f:repeatable var="loc" items="${scm.locations}">                                               
+      <table width="100%">                                                                         
+        <f:entry title="Repository location">                                                      
+          <input class="setting-input validated" name="location_remote"                            
+            type="text" value="${loc.remote}"                                                      
+            checkUrl="'${rootURL}/scm/SubversionSCM/svnRemoteLocationCheck?value='+this.value"/>   
+        </f:entry>                                                                                 
+        <f:entry title="Local module directory (optional)">                                        
+          <input class="setting-input validated" name="location_local"                             
+            type="text" value="${loc.local}"                                                        
+            checkUrl="'${rootURL}/scm/SubversionSCM/svnLocalLocationCheck?value='+this.value"/>     
+        </f:entry>                                                                                  
+        <f:entry title="">                                                                          
+          <div align="right">                                                                       
+            <f:repeatableDeleteButton />                                                            
+          </div>                                                                                    
+        </f:entry>                                                                                  
+      </table>                                                                                      
+    </f:repeatable>                                                                                 
+  </f:entry>                                                                                        
+   <f:entry title="Use update"                                                                                                                                                                                        
+     description="                                                                                                
+       If checked, Hudson will use 'svn update' whenever possible, making the build faster.                       
+       But this causes the artifacts from the previous build to remain when a new build starts.">                 
+     <f:checkbox name="svn_use_update" checked="${scm.useUpdate}"/>                                               
+   </f:entry>                                                                                                     
+ </j:jelly>                                                                                                          
Index: src/main/java/hudson/scm/SubversionSCM.java
===================================================================
RCS file: /cvs/hudson/hudson/main/core/src/main/java/hudson/scm/SubversionSCM.java,v
retrieving revision 1.29
diff -u -r1.29 SubversionSCM.java
--- src/main/java/hudson/scm/SubversionSCM.java	22 Feb 2007 02:16:55 -0000	1.29
+++ src/main/java/hudson/scm/SubversionSCM.java	19 Mar 2007 09:06:31 -0000
@@ -1,10 +1,10 @@
 package hudson.scm;
 
+import static hudson.Util.fixNull;
 import hudson.FilePath;
-import hudson.FilePath.FileCallable;
 import hudson.Launcher;
 import hudson.Util;
-import static hudson.Util.fixNull;
+import hudson.FilePath.FileCallable;
 import hudson.model.AbstractBuild;
 import hudson.model.AbstractProject;
 import hudson.model.BuildListener;
@@ -15,6 +15,34 @@
 import hudson.remoting.VirtualChannel;
 import hudson.util.FormFieldValidator;
 import hudson.util.Scrambler;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletException;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
 import org.kohsuke.stapler.StaplerRequest;
 import org.kohsuke.stapler.StaplerResponse;
 import org.tmatesoft.svn.core.SVNErrorMessage;
@@ -40,647 +68,977 @@
 import org.tmatesoft.svn.core.wc.xml.SVNXMLLogHandler;
 import org.xml.sax.helpers.LocatorImpl;
 
-import javax.servlet.ServletException;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamResult;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.io.Serializable;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.StringTokenizer;
-import java.util.logging.Logger;
-import java.util.logging.Level;
-
 /**
  * Subversion.
- *
+ * 
  * Check http://svn.collab.net/repos/svn/trunk/subversion/svn/schema/ for
  * various output formats.
- *
+ * 
  * @author Kohsuke Kawaguchi
  */
 public class SubversionSCM extends SCM implements Serializable {
-    private final String modules;
-    private boolean useUpdate;
-    private String username;
-
-    /**
-     * @deprecated
-     *      No longer in use but left for serialization compatibility.
-     */
-    private transient String otherOptions;
-
-    SubversionSCM(String modules, boolean useUpdate, String username) {
-        StringBuilder normalizedModules = new StringBuilder();
-        StringTokenizer tokens = new StringTokenizer(modules);
-        while(tokens.hasMoreTokens()) {
-            if(normalizedModules.length()>0)    normalizedModules.append(' ');
-            String m = tokens.nextToken();
-            if(m.endsWith("/"))
-                // the normalized name is always without the trailing '/'
-                m = m.substring(0,m.length()-1);
-            normalizedModules.append(m);
-       }
-
-        this.modules = normalizedModules.toString();
-        this.useUpdate = useUpdate;
-        this.username = nullify(username);
-    }
-
-    /**
-     * Whitespace-separated list of SVN URLs that represent
-     * modules to be checked out.
-     */
-    public String getModules() {
-        return modules;
-    }
-
-    public boolean isUseUpdate() {
-        return useUpdate;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    private Collection<String> getModuleDirNames() {
-        List<String> dirs = new ArrayList<String>();
-        StringTokenizer tokens = new StringTokenizer(modules);
-        while(tokens.hasMoreTokens()) {
-            dirs.add(getLastPathComponent(tokens.nextToken()));
-        }
-        return dirs;
-    }
-
-    private boolean calcChangeLog(AbstractBuild<?, ?> build, File changelogFile, BuildListener listener) throws IOException {
-        if(build.getPreviousBuild()==null) {
-            // nothing to compare against
-            return createEmptyChangeLog(changelogFile, listener, "log");
-        }
-
-        PrintStream logger = listener.getLogger();
-
-        Map<String,Long> previousRevisions = parseRevisionFile(build.getPreviousBuild());
-        Map<String,Long> thisRevisions     = parseRevisionFile(build);
-
-        boolean changelogFileCreated = false;
-
-        SVNLogClient svnlc = createSvnClientManager(getDescriptor().createAuthenticationProvider()).getLogClient();
-
-        TransformerHandler th = createTransformerHandler();
-        th.setResult(new StreamResult(changelogFile));
-        SVNXMLLogHandler logHandler = new SVNXMLLogHandler(th);
-        // work around for http://svnkit.com/tracker/view.php?id=175
-        th.setDocumentLocator(DUMMY_LOCATOR);
-        logHandler.startDocument();
-
-
-        StringTokenizer tokens = new StringTokenizer(modules);
-        while(tokens.hasMoreTokens()) {
-            String url = tokens.nextToken();
-            Long prevRev = previousRevisions.get(url);
-            if(prevRev==null) {
-                logger.println("no revision recorded for "+url+" in the previous build");
-                continue;
-            }
-            Long thisRev = thisRevisions.get(url);
-            if(thisRev.equals(prevRev)) {
-                logger.println("no change for "+url+" since the previous build");
-                continue;
-            }
-
-            try {
-                svnlc.doLog(SVNURL.parseURIEncoded(url),null,
-                SVNRevision.create(prevRev), SVNRevision.create(prevRev+1),
-                    SVNRevision.create(thisRev),
-                    false, true, Long.MAX_VALUE, logHandler);
-            } catch (SVNException e) {
-                e.printStackTrace(listener.error("revision check failed on "+url));
-            }
-            changelogFileCreated = true;
-        }
-
-        if(changelogFileCreated) {
-            logHandler.endDocument();
-        }
-
-        if(!changelogFileCreated)
-            createEmptyChangeLog(changelogFile, listener, "log");
-
-        return true;
-    }
-
-    /**
-     * Creates an identity transformer.
-     */
-    private static TransformerHandler createTransformerHandler() {
-        try {
-            return ((SAXTransformerFactory) SAXTransformerFactory.newInstance()).newTransformerHandler();
-        } catch (TransformerConfigurationException e) {
-            throw new Error(e); // impossible
-        }
-    }
-
-    /*package*/ static Map<String,Long> parseRevisionFile(AbstractBuild build) throws IOException {
-        Map<String,Long> revisions = new HashMap<String,Long>(); // module -> revision
-        {// read the revision file of the last build
-            File file = getRevisionFile(build);
-            if(!file.exists())
-                // nothing to compare against
-                return revisions;
-
-            BufferedReader br = new BufferedReader(new FileReader(file));
-            String line;
-            while((line=br.readLine())!=null) {
-                int index = line.lastIndexOf('/');
-                if(index<0) {
-                    continue;   // invalid line?
-                }
-                try {
-                    revisions.put(line.substring(0,index), Long.parseLong(line.substring(index+1)));
-                } catch (NumberFormatException e) {
-                    // perhaps a corrupted line. ignore
-                }
-            }
-        }
-
-        return revisions;
-    }
-
-    public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, final BuildListener listener, File changelogFile) throws IOException, InterruptedException {
-        if(!checkout(launcher,workspace, listener))
-            return false;
-
-        // write out the revision file
-        PrintWriter w = new PrintWriter(new FileOutputStream(getRevisionFile(build)));
-        try {
-            Map<String,SvnInfo> revMap = buildRevisionMap(workspace, listener);
-            for (Entry<String,SvnInfo> e : revMap.entrySet()) {
-                w.println( e.getKey() +'/'+ e.getValue().revision );
-            }
-        } finally {
-            w.close();
-        }
-
-        return calcChangeLog(build, changelogFile, listener);
-    }
-
-    public boolean checkout(Launcher launcher, FilePath workspace, final TaskListener listener) throws IOException, InterruptedException {
-        if(useUpdate && isUpdatable(workspace, listener)) {
-            return update(launcher,workspace,listener);
-        } else {
-            final ISVNAuthenticationProvider authProvider = getDescriptor().createAuthenticationProvider();
-            return workspace.act(new FileCallable<Boolean>() {
-                public Boolean invoke(File ws, VirtualChannel channel) throws IOException {
-                    Util.deleteContentsRecursive(ws);
-                    SVNUpdateClient svnuc = createSvnClientManager(authProvider).getUpdateClient();
-                    svnuc.setEventHandler(new SubversionUpdateEventHandler(listener));
-
-                    StringTokenizer tokens = new StringTokenizer(modules);
-                    while(tokens.hasMoreTokens()) {
-                        try {
-                            SVNURL url = SVNURL.parseURIEncoded(tokens.nextToken());
-                            listener.getLogger().println("Checking out "+url);
-
-                            svnuc.doCheckout(url, new File(ws, getLastPathComponent(url.getPath())), SVNRevision.HEAD, SVNRevision.HEAD, true );
-                        } catch (SVNException e) {
-                            e.printStackTrace(listener.error("Error in subversion"));
-                            return false;
-                        }
-                    }
-
-                    return true;
-                }
-            });
-        }
-    }
-
-    /**
-     * Creates {@link SVNClientManager}.
-     *
-     * <p>
-     * This method must be executed on the slave where svn operations are performed.
-     *
-     * @param authProvider
-     *      The value obtained from {@link DescriptorImpl#createAuthenticationProvider()}.
-     *      If the operation runs on slaves,
-     *      (and properly remoted, if the svn operations run on slaves.)
-     */
-    private static SVNClientManager createSvnClientManager(ISVNAuthenticationProvider authProvider) {
-        ISVNAuthenticationManager sam = SVNWCUtil.createDefaultAuthenticationManager();
-        sam.setAuthenticationProvider(authProvider);
-        return SVNClientManager.newInstance(SVNWCUtil.createDefaultOptions(true),sam);
-    }
-
-    public static final class SvnInfo implements Serializable {
-        /**
-         * Decoded repository URL.
-         */
-        final String url;
-        final long revision;
-
-        public SvnInfo(String url, long revision) {
-            this.url = url;
-            this.revision = revision;
-        }
-
-        public SvnInfo(SVNInfo info) {
-            this( info.getURL().toDecodedString(), info.getCommittedRevision().getNumber() );
-        }
-
-        public SVNURL getSVNURL() throws SVNException {
-            return SVNURL.parseURIDecoded(url);
-        }
-
-        private static final long serialVersionUID = 1L;
-    }
-
-    /**
-     * Gets the SVN metadata for the given local workspace.
-     *
-     * @param workspace
-     *      The target to run "svn info".
-     */
-    private SVNInfo parseSvnInfo(File workspace, ISVNAuthenticationProvider authProvider) throws SVNException {
-        SVNWCClient svnWc = createSvnClientManager(authProvider).getWCClient();
-        return svnWc.doInfo(workspace,SVNRevision.WORKING);
-    }
-
-    /**
-     * Gets the SVN metadata for the remote repository.
-     *
-     * @param remoteUrl
-     *      The target to run "svn info".
-     */
-    private SVNInfo parseSvnInfo(SVNURL remoteUrl, ISVNAuthenticationProvider authProvider) throws SVNException {
-        SVNWCClient svnWc = createSvnClientManager(authProvider).getWCClient();
-        return svnWc.doInfo(remoteUrl, SVNRevision.HEAD, SVNRevision.HEAD);
-    }
-
-    /**
-     * Checks .svn files in the workspace and finds out revisions of the modules
-     * that the workspace has.
-     *
-     * @return
-     *      null if the parsing somehow fails. Otherwise a map from the repository URL to revisions.
-     */
-    private Map<String,SvnInfo> buildRevisionMap(FilePath workspace, final TaskListener listener) throws IOException, InterruptedException {
-        final ISVNAuthenticationProvider authProvider = getDescriptor().createAuthenticationProvider();
-        return workspace.act(new FileCallable<Map<String,SvnInfo>>() {
-            public Map<String,SvnInfo> invoke(File ws, VirtualChannel channel) throws IOException {
-                Map<String/*module name*/,SvnInfo> revisions = new HashMap<String,SvnInfo>();
-
-                SVNWCClient svnWc = createSvnClientManager(authProvider).getWCClient();
-                // invoke the "svn info"
-                for( String module : getModuleDirNames() ) {
-                    try {
-                        SvnInfo info = new SvnInfo(svnWc.doInfo(new File(ws,module),SVNRevision.WORKING));
-                        revisions.put(info.url,info);
-                    } catch (SVNException e) {
-                        e.printStackTrace(listener.error("Failed to parse svn info for "+module));
-                    }
-                }
-
-                return revisions;
-            }
-        });
-    }
-
-    /**
-     * Gets the file that stores the revision.
-     */
-    private static File getRevisionFile(AbstractBuild build) {
-        return new File(build.getRootDir(),"revision.txt");
-    }
-
-    public boolean update(Launcher launcher, FilePath workspace, final TaskListener listener) throws IOException, InterruptedException {
-        final ISVNAuthenticationProvider authProvider = getDescriptor().createAuthenticationProvider();
-        return workspace.act(new FileCallable<Boolean>() {
-            public Boolean invoke(File ws, VirtualChannel channel) throws IOException {
-                SVNUpdateClient svnuc = createSvnClientManager(authProvider).getUpdateClient();
-                svnuc.setEventHandler(new SubversionUpdateEventHandler(listener));
-
-                StringTokenizer tokens = new StringTokenizer(modules);
-                while(tokens.hasMoreTokens()) {
-                    try {
-                        String url = tokens.nextToken();
-                        listener.getLogger().println("Updating "+url);
-                        svnuc.doUpdate(new File(ws, getLastPathComponent(url)), SVNRevision.HEAD, true );
-                    } catch (SVNException e) {
-                        e.printStackTrace(listener.error("Error in subversion"));
-                        return false;
-                    }
-                }
-                return true;
-            }
-        });
-    }
-
-    /**
-     * Returns true if we can use "svn update" instead of "svn checkout"
-     */
-    private boolean isUpdatable(FilePath workspace, final TaskListener listener) throws IOException, InterruptedException {
-        final ISVNAuthenticationProvider authProvider = getDescriptor().createAuthenticationProvider();
-
-        return workspace.act(new FileCallable<Boolean>() {
-            public Boolean invoke(File ws, VirtualChannel channel) throws IOException {
-                StringTokenizer tokens = new StringTokenizer(modules);
-                while(tokens.hasMoreTokens()) {
-                    String url = tokens.nextToken();
-                    String moduleName = getLastPathComponent(url);
-                    File module = new File(ws,moduleName);
-
-                    if(!module.exists()) {
-                        listener.getLogger().println("Checking out a fresh workspace because "+module+" doesn't exist");
-                        return false;
-                    }
-
-                    try {
-                        SvnInfo svnInfo = new SvnInfo(parseSvnInfo(module,authProvider));
-                        if(!svnInfo.url.equals(url)) {
-                            listener.getLogger().println("Checking out a fresh workspace because the workspace is not "+url);
-                            return false;
-                        }
-                    } catch (SVNException e) {
-                        listener.getLogger().println("Checking out a fresh workspace because Hudson failed to detect the current workspace "+module);
-                        e.printStackTrace(listener.error(e.getMessage()));
-                        return false;
-                    }
-                }
-                return true;
-            }
-        });
-    }
-
-    public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener) throws IOException, InterruptedException {
-        // current workspace revision
-        Map<String,SvnInfo> wsRev = buildRevisionMap(workspace, listener);
-
-        ISVNAuthenticationProvider authProvider = getDescriptor().createAuthenticationProvider();
-
-        // check the corresponding remote revision
-        for (SvnInfo localInfo : wsRev.values()) {
-            try {
-                SvnInfo remoteInfo = new SvnInfo(parseSvnInfo(localInfo.getSVNURL(),authProvider));
-                listener.getLogger().println("Revision:"+remoteInfo.revision);
-                if(remoteInfo.revision > localInfo.revision)
-                    return true;    // change found
-            } catch (SVNException e) {
-                e.printStackTrace(listener.error("Failed to check repository revision for "+localInfo.url));
-            }
-        }
-
-        return false; // no change
-    }
-
-    public ChangeLogParser createChangeLogParser() {
-        return new SubversionChangeLogParser();
-    }
-
-
-    public DescriptorImpl getDescriptor() {
-        return DescriptorImpl.DESCRIPTOR;
-    }
-
-    public void buildEnvVars(Map<String,String> env) {
-        // no environment variable
-    }
-
-    public FilePath getModuleRoot(FilePath workspace) {
-        String s;
-
-        // if multiple URLs are specified, pick the first one
-        int idx = modules.indexOf(' ');
-        if(idx>=0)  s = modules.substring(0,idx);
-        else        s = modules;
-
-        return workspace.child(getLastPathComponent(s));
-    }
-
-    private static String getLastPathComponent(String s) {
-        String[] tokens = s.split("/");
-        return tokens[tokens.length-1]; // return the last token
-    }
-
-    public static final class DescriptorImpl extends Descriptor<SCM> {
-        public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
-
-        /**
-         * Path to <tt>svn.exe</tt>. Null to default.
-         *
-         * @deprecated
-         *      No longer in use.
-         */
-        private volatile String svnExe;
-
-        /**
-         * SVN authentication realm to its associated credentials.
-         */
-        private final Map<String,Credential> credentials = new Hashtable<String,Credential>();
-
-        /**
-         * Stores {@link SVNAuthentication} for a single realm.
-         */
-        private static abstract class Credential implements Serializable {
-            abstract SVNAuthentication createSVNAuthentication();
-        }
-
-        private static final class PasswordCredential extends Credential {
-            private final String userName;
-            private final String password; // scrambled by base64
-
-            public PasswordCredential(String userName, String password) {
-                this.userName = userName;
-                this.password = Scrambler.scramble(password);
-            }
-
-            @Override
-            SVNPasswordAuthentication createSVNAuthentication() {
-                return new SVNPasswordAuthentication(userName,Scrambler.descramble(password),false);
-            }
-        }
-
-        /**
-         * Remoting interface that allows remote {@link ISVNAuthenticationProvider}
-         * to read from local {@link DescriptorImpl#credentials}.
-         */
-        private interface RemotableSVNAuthenticationProvider {
-            Credential getCredential(String realm);
-        }
-
-        private final class RemotableSVNAuthenticationProviderImpl implements RemotableSVNAuthenticationProvider, Serializable {
-            public Credential getCredential(String realm) {
-                return credentials.get(realm);
-            }
-
-            /**
-             * When sent to the remote node, send a proxy.
-             */
-            private Object writeReplace() {
-                return Channel.current().export(RemotableSVNAuthenticationProvider.class, this);
-            }
-        }
-
-        /**
-         * See {@link DescriptorImpl#createAuthenticationProvider()}.
-         */
-        private static final class SVNAuthenticationProviderImpl implements ISVNAuthenticationProvider, Serializable {
-            private final RemotableSVNAuthenticationProvider source;
-
-            public SVNAuthenticationProviderImpl(RemotableSVNAuthenticationProvider source) {
-                this.source = source;
-            }
-
-            public SVNAuthentication requestClientAuthentication(String kind, SVNURL url, String realm, SVNErrorMessage errorMessage, SVNAuthentication previousAuth, boolean authMayBeStored) {
-                Credential cred = source.getCredential(realm);
-                if(cred==null)  return null;
-                return cred.createSVNAuthentication();
-            }
-
-            public int acceptServerAuthentication(SVNURL url, String realm, Object certificate, boolean resultMayBeStored) {
-                return ACCEPTED_TEMPORARY;
-            }
-
-            private static final long serialVersionUID = 1L;
-        }
-
-        private DescriptorImpl() {
-            super(SubversionSCM.class);
-            load();
-        }
-
-        public String getDisplayName() {
-            return "Subversion";
-        }
-
-        public SCM newInstance(StaplerRequest req) {
-            return new SubversionSCM(
-                req.getParameter("svn_modules"),
-                req.getParameter("svn_use_update")!=null,
-                req.getParameter("svn_username")
-            );
-        }
-
-        /**
-         * Creates {@link ISVNAuthenticationProvider} backed by {@link #credentials}.
-         * This method must be invoked on the master, but the returned object is remotable.
-         */
-        public ISVNAuthenticationProvider createAuthenticationProvider() {
-            return new SVNAuthenticationProviderImpl(new RemotableSVNAuthenticationProviderImpl());
-        }
-
-        /**
-         * Used in the job configuration page to check if authentication for the SVN URLs
-         * are available.
-         */
-        public void doAuthenticationCheck(final StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
-            new FormFieldValidator(req,rsp,true) {
-                protected void check() throws IOException, ServletException {
-                    StringTokenizer tokens = new StringTokenizer(fixNull(request.getParameter("value")));
-                    String message="";
-
-                    while(tokens.hasMoreTokens()) {
-                        String url = tokens.nextToken();
-
-                        try {
-                            SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(url));
-
-                            ISVNAuthenticationManager sam = SVNWCUtil.createDefaultAuthenticationManager();
-                            sam.setAuthenticationProvider(createAuthenticationProvider());
-                            repository.setAuthenticationManager(sam);
-                            
-                            repository.testConnection();
-                        } catch (SVNException e) {
-                            StringWriter sw = new StringWriter();
-                            e.printStackTrace(new PrintWriter(sw));
-
-                            message += "Unable to access "+url+" : "+Util.escape( e.getErrorMessage().getFullMessage());
-                            message += " <a href='#' id=svnerrorlink onclick='javascript:" +
-                                "document.getElementById(\"svnerror\").style.display=\"block\";" +
-                                "document.getElementById(\"svnerrorlink\").style.display=\"none\";" +
-                                "return false;'>(show details)</a>";
-                            message += "<pre id=svnerror style='display:none'>"+sw+"</pre>";
-                            message += " (Maybe you need to <a href='"+req.getContextPath()+"/scm/SubversionSCM/enterCredential?"+url+"'>enter credential</a>?)";
-                            message += "<br>";
-                            logger.log(Level.INFO, "Failed to access subversion repository "+url,e);
-                        }
-                    }
-
-                    if(message.length()==0)
-                        ok();
-                    else
-                        error(message);
-                }
-            }.process();
-        }
-
-        /**
-         * Submits the authentication info.
-         */
-        public void doPostCredential(final StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
-            final String url = req.getParameter("url");
-            final String username = req.getParameter("username");
-            final String password = req.getParameter("password");
-
-            try {
-                // the way it works with SVNKit is that
-                // 1) svnkit calls AuthenticationManager asking for a credential.
-                //    this is when we can see the 'realm', which identifies the user domain.
-                // 2) DefaultSVNAuthenticationManager returns the username and password we set below
-                // 3) if the authentication is successful, svnkit calls back acknowledgeAuthentication
-                //    (so we store the password info here)
-                SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(url));
-                repository.setAuthenticationManager(new DefaultSVNAuthenticationManager(SVNWCUtil.getDefaultConfigurationDirectory(),true,username,password) {
-                    public void acknowledgeAuthentication(boolean accepted, String kind, String realm, SVNErrorMessage errorMessage, SVNAuthentication authentication) throws SVNException {
-                        if(accepted) {
-                            credentials.put(realm,new PasswordCredential(username,password));
-                            save();
-                        }
-                        super.acknowledgeAuthentication(accepted, kind, realm, errorMessage, authentication);
-                    }
-                });
-                repository.testConnection();
-                rsp.sendRedirect("credentialOK");
-            } catch (SVNException e) {
-                req.setAttribute("message",e.getErrorMessage());
-                rsp.forward(Hudson.getInstance(),"error",req);
-            }
-        }
-
-        static { new Initializer(); }
-    }
-
-    private static final long serialVersionUID = 1L;
-
-    private static final Logger logger = Logger.getLogger(SubversionSCM.class.getName());
-
-    private static final LocatorImpl DUMMY_LOCATOR = new LocatorImpl();
-
-    static {
-        new Initializer();
-        DUMMY_LOCATOR.setLineNumber(-1);
-        DUMMY_LOCATOR.setColumnNumber(-1);
-    }
-    
-    private static final class Initializer {
-        static {
-            DAVRepositoryFactory.setup();   // http, https
-            SVNRepositoryFactoryImpl.setup();   // svn, svn+xxx
-            FSRepositoryFactory.setup();    // file
-        }
-    }
+	@Deprecated
+	private String modules;
+
+	/**
+	 * the locations field is used to store all configured SVN locations (with
+	 * their local and remote part). Direct access to this filed should be
+	 * avoided and the getLocations() method should be used instead. This is
+	 * needed to make importing of old hudson-configurations possible as
+	 * getLocations() will check if the modules field has been set and import
+	 * the data.
+	 * 
+	 * @since 1.64
+	 */
+	private ModuleLocation[] locations = new ModuleLocation[0];
+
+	private boolean useUpdate;
+
+	private String username;
+
+	/**
+	 * @deprecated No longer in use but left for serialization compatibility.
+	 */
+	private transient String otherOptions;
+
+	SubversionSCM(String[] remoteLocations, String[] localLocations,
+			boolean useUpdate, String username, String otherOptions) {
+
+		if (remoteLocations != null && localLocations != null) {
+			int entries = Math.min(remoteLocations.length,
+					localLocations.length);
+
+			ModuleLocation[] l = new ModuleLocation[entries];
+
+			for (int i = 0; i < l.length; i++) {
+
+				// the remote (repository) location
+				String remoteLoc = nullify(remoteLocations[i]);
+				// the local filesystem location (directory to checkout into)
+				String localLoc = nullify(localLocations[i]);
+
+				if (remoteLoc != null) {
+					remoteLoc = remoteLoc.trim();
+
+					if (remoteLoc.charAt(remoteLoc.length() - 1) == '/')
+						remoteLoc.substring(0, remoteLoc.length() - 1);
+
+					if (localLoc == null)
+						localLoc = getLastPathComponent(remoteLoc);
+
+					String errorMessage = null;
+					if ((errorMessage = validateRemoteLocation(remoteLoc)) == null
+							&& (errorMessage = validateLocalLocation(remoteLoc)) == null)
+						// the location is not configured properly
+						l[i] = new ModuleLocation(remoteLoc, localLoc,
+								errorMessage);
+					else
+						// everything fine
+						l[i] = new ModuleLocation(remoteLoc, localLoc);
+				}
+			}
+			locations = l;
+		}
+
+		this.useUpdate = useUpdate;
+		this.username = nullify(username);
+	}
+
+	/**
+	 * this method is beeing kept for compatibility reasons with older hudson
+	 * installations. All data is beeing stored in the {@link #locations} filed
+	 * which is basically a list of {@link ModuleLocations}
+	 */
+	@Deprecated
+	public String getModules() {
+		return modules;
+	}
+
+	/**
+	 * this method is beeing kept for compatibility reasons with older hudson
+	 * installations. All data is beeing stored in the {@link #locations} filed
+	 * which is basically a list of {@link ModuleLocations}
+	 */
+	@Deprecated
+	public void setModules(String modules) {
+		this.modules = modules;
+	}
+
+	/**
+	 * list of all configured svn locations
+	 * 
+	 * @since 1.64
+	 */
+	public ModuleLocation[] getLocations() {
+		// check if we've got a old location
+		if (modules != null) {
+			// import the old configuration
+			Vector<ModuleLocation> oldLocations = new Vector<ModuleLocation>();
+			StringTokenizer tokens = new StringTokenizer(modules);
+			while (tokens.hasMoreTokens()) {
+				// the remote (repository location)
+				String remoteLoc = tokens.nextToken();
+				if (remoteLoc.endsWith("/"))
+					// the normalized name is always without the trailing '/'
+					remoteLoc = remoteLoc.substring(0, remoteLoc.length() - 1);
+
+				// the location in the local filesystem
+				String LocalLoc = getLastPathComponent(remoteLoc);
+
+				// check if there are errors in the configuration
+				String errorMessage = null;
+				if ((errorMessage = validateRemoteLocation(remoteLoc)) == null
+						&& (errorMessage = validateLocalLocation(remoteLoc)) == null)
+					// the location is not configured properly
+					oldLocations.add(new ModuleLocation(remoteLoc, LocalLoc,
+							errorMessage));
+				else
+					// everything fine
+					oldLocations.add(new ModuleLocation(remoteLoc, LocalLoc));
+			}
+
+			locations = oldLocations.toArray(new ModuleLocation[oldLocations
+					.size()]);
+			modules = null;
+		}
+		return locations;
+	}
+
+	public boolean isUseUpdate() {
+		return useUpdate;
+	}
+
+	public String getUsername() {
+		return username;
+	}
+
+	private Collection<String> getModuleDirNames() {
+		List<String> dirs = new ArrayList<String>();
+		for (ModuleLocation l : getLocations()) {
+			dirs.add(l.getLocal());
+		}
+		return dirs;
+	}
+
+	private boolean calcChangeLog(AbstractBuild<?, ?> build,
+			File changelogFile, BuildListener listener) throws IOException {
+		if (build.getPreviousBuild() == null) {
+			// nothing to compare against
+			return createEmptyChangeLog(changelogFile, listener, "log");
+		}
+
+		PrintStream logger = listener.getLogger();
+
+		Map<String, Long> previousRevisions = parseRevisionFile(build
+				.getPreviousBuild());
+		Map<String, Long> thisRevisions = parseRevisionFile(build);
+
+		boolean changelogFileCreated = false;
+
+		SVNLogClient svnlc = createSvnClientManager(
+				getDescriptor().createAuthenticationProvider()).getLogClient();
+
+		TransformerHandler th = createTransformerHandler();
+		th.setResult(new StreamResult(changelogFile));
+		SVNXMLLogHandler logHandler = new SVNXMLLogHandler(th);
+		// work around for http://svnkit.com/tracker/view.php?id=175
+		th.setDocumentLocator(DUMMY_LOCATOR);
+		logHandler.startDocument();
+
+		for (ModuleLocation l : getLocations()) {
+			String url = l.getRemote();
+			Long prevRev = previousRevisions.get(url);
+			if (prevRev == null) {
+				logger.println("no revision recorded for " + url
+						+ " in the previous build");
+				continue;
+			}
+			Long thisRev = thisRevisions.get(url);
+			if (thisRev.equals(prevRev)) {
+				logger.println("no change for " + url
+						+ " since the previous build");
+				continue;
+			}
+
+			try {
+				svnlc.doLog(SVNURL.parseURIEncoded(url), null, SVNRevision
+						.create(prevRev), SVNRevision.create(prevRev + 1),
+						SVNRevision.create(thisRev), false, true,
+						Long.MAX_VALUE, logHandler);
+			} catch (SVNException e) {
+				e.printStackTrace(listener.error("revision check failed on "
+						+ url));
+			}
+			changelogFileCreated = true;
+		}
+
+		if (changelogFileCreated) {
+			logHandler.endDocument();
+		}
+
+		if (!changelogFileCreated)
+			createEmptyChangeLog(changelogFile, listener, "log");
+
+		return true;
+	}
+
+	/**
+	 * Creates an identity transformer.
+	 */
+	private static TransformerHandler createTransformerHandler() {
+		try {
+			return ((SAXTransformerFactory) SAXTransformerFactory.newInstance())
+					.newTransformerHandler();
+		} catch (TransformerConfigurationException e) {
+			throw new Error(e); // impossible
+		}
+	}
+
+	/* package */static Map<String, Long> parseRevisionFile(AbstractBuild build)
+			throws IOException {
+		Map<String, Long> revisions = new HashMap<String, Long>(); // module ->
+		// revision
+		{// read the revision file of the last build
+			File file = getRevisionFile(build);
+			if (!file.exists())
+				// nothing to compare against
+				return revisions;
+
+			BufferedReader br = new BufferedReader(new FileReader(file));
+			String line;
+			while ((line = br.readLine()) != null) {
+				int index = line.lastIndexOf('/');
+				if (index < 0) {
+					continue; // invalid line?
+				}
+				try {
+					revisions.put(line.substring(0, index), Long.parseLong(line
+							.substring(index + 1)));
+				} catch (NumberFormatException e) {
+					// perhaps a corrupted line. ignore
+				}
+			}
+		}
+
+		return revisions;
+	}
+
+	public boolean checkout(AbstractBuild build, Launcher launcher,
+			FilePath workspace, final BuildListener listener, File changelogFile)
+			throws IOException, InterruptedException {
+		if (!checkout(launcher, workspace, listener))
+			return false;
+
+		// write out the revision file
+		PrintWriter w = new PrintWriter(new FileOutputStream(
+				getRevisionFile(build)));
+		try {
+			Map<String, SvnInfo> revMap = buildRevisionMap(workspace, listener);
+			for (Entry<String, SvnInfo> e : revMap.entrySet()) {
+				w.println(e.getKey() + '/' + e.getValue().revision);
+			}
+		} finally {
+			w.close();
+		}
+
+		return calcChangeLog(build, changelogFile, listener);
+	}
+
+	public boolean checkout(Launcher launcher, FilePath workspace,
+			final TaskListener listener) throws IOException,
+			InterruptedException {
+		if (useUpdate && isUpdatable(workspace, listener)) {
+			return update(launcher, workspace, listener);
+		} else {
+			final ISVNAuthenticationProvider authProvider = getDescriptor()
+					.createAuthenticationProvider();
+			return workspace.act(new FileCallable<Boolean>() {
+				public Boolean invoke(File ws, VirtualChannel channel)
+						throws IOException {
+					Util.deleteContentsRecursive(ws);
+					SVNUpdateClient svnuc = createSvnClientManager(authProvider)
+							.getUpdateClient();
+					svnuc.setEventHandler(new SubversionUpdateEventHandler(
+							listener));
+
+					for (ModuleLocation l : getLocations()) {
+						if (l.getErrorMessage() != null) {
+							listener.fatalError("Unable to perform checkout: "
+									+ l.getErrorMessage()
+									+ ". Please validate your configuration.");
+							return false;
+						}
+						try {
+							SVNURL url = SVNURL.parseURIEncoded(l.getRemote());
+							listener.getLogger().println("Checking out " + url);
+
+							svnuc.doCheckout(url, new File(ws, l.getLocal()),
+									SVNRevision.HEAD, SVNRevision.HEAD, true);
+						} catch (SVNException e) {
+							e.printStackTrace(listener
+									.error("Error in subversion"));
+							return false;
+						}
+					}
+
+					return true;
+				}
+			});
+		}
+	}
+
+	/**
+	 * Creates {@link SVNClientManager}.
+	 * 
+	 * <p>
+	 * This method must be executed on the slave where svn operations are
+	 * performed.
+	 * 
+	 * @param authProvider
+	 *            The value obtained from
+	 *            {@link DescriptorImpl#createAuthenticationProvider()}. If the
+	 *            operation runs on slaves, (and properly remoted, if the svn
+	 *            operations run on slaves.)
+	 */
+	private static SVNClientManager createSvnClientManager(
+			ISVNAuthenticationProvider authProvider) {
+		ISVNAuthenticationManager sam = SVNWCUtil
+				.createDefaultAuthenticationManager();
+		sam.setAuthenticationProvider(authProvider);
+		return SVNClientManager.newInstance(SVNWCUtil
+				.createDefaultOptions(true), sam);
+	}
+
+	public static final class SvnInfo implements Serializable {
+		/**
+		 * Decoded repository URL.
+		 */
+		final String url;
+
+		final long revision;
+
+		public SvnInfo(String url, long revision) {
+			this.url = url;
+			this.revision = revision;
+		}
+
+		public SvnInfo(SVNInfo info) {
+			this(info.getURL().toDecodedString(), info.getCommittedRevision()
+					.getNumber());
+		}
+
+		public SVNURL getSVNURL() throws SVNException {
+			return SVNURL.parseURIDecoded(url);
+		}
+
+		private static final long serialVersionUID = 1L;
+	}
+
+	/**
+	 * Gets the SVN metadata for the given local workspace.
+	 * 
+	 * @param workspace
+	 *            The target to run "svn info".
+	 */
+	private SVNInfo parseSvnInfo(File workspace,
+			ISVNAuthenticationProvider authProvider) throws SVNException {
+		SVNWCClient svnWc = createSvnClientManager(authProvider).getWCClient();
+		return svnWc.doInfo(workspace, SVNRevision.WORKING);
+	}
+
+	/**
+	 * Gets the SVN metadata for the remote repository.
+	 * 
+	 * @param remoteUrl
+	 *            The target to run "svn info".
+	 */
+	private SVNInfo parseSvnInfo(SVNURL remoteUrl,
+			ISVNAuthenticationProvider authProvider) throws SVNException {
+		SVNWCClient svnWc = createSvnClientManager(authProvider).getWCClient();
+		return svnWc.doInfo(remoteUrl, SVNRevision.HEAD, SVNRevision.HEAD);
+	}
+
+	/**
+	 * Checks .svn files in the workspace and finds out revisions of the modules
+	 * that the workspace has.
+	 * 
+	 * @return null if the parsing somehow fails. Otherwise a map from the
+	 *         repository URL to revisions.
+	 */
+	private Map<String, SvnInfo> buildRevisionMap(FilePath workspace,
+			final TaskListener listener) throws IOException,
+			InterruptedException {
+		final ISVNAuthenticationProvider authProvider = getDescriptor()
+				.createAuthenticationProvider();
+		return workspace.act(new FileCallable<Map<String, SvnInfo>>() {
+			public Map<String, SvnInfo> invoke(File ws, VirtualChannel channel)
+					throws IOException {
+				Map<String/* module name */, SvnInfo> revisions = new HashMap<String, SvnInfo>();
+
+				SVNWCClient svnWc = createSvnClientManager(authProvider)
+						.getWCClient();
+				// invoke the "svn info"
+				for (String module : getModuleDirNames()) {
+					try {
+						SvnInfo info = new SvnInfo(svnWc.doInfo(new File(ws,
+								module), SVNRevision.WORKING));
+						revisions.put(info.url, info);
+					} catch (SVNException e) {
+						e
+								.printStackTrace(listener
+										.error("Failed to parse svn info for "
+												+ module));
+					}
+				}
+
+				return revisions;
+			}
+		});
+	}
+
+	/**
+	 * Gets the file that stores the revision.
+	 */
+	private static File getRevisionFile(AbstractBuild build) {
+		return new File(build.getRootDir(), "revision.txt");
+	}
+
+	/**
+	 * this method is beeing used to validate the repository location. If
+	 * <code>null</code> is beeing returned the location is all fine and has
+	 * been validated successfully. A String instance beeing returned indicates
+	 * that the validation failed and the String will contain a errormessage
+	 * 
+	 * @param remoteLocation
+	 *            the location to be validated
+	 * @return <code>null</code> if the location is valid or any instance of a
+	 *         String which represents a failure message if the validation
+	 *         failed
+	 */
+	private static String validateRemoteLocation(String remoteLocation) {
+		String v = Util.nullify(remoteLocation);
+		System.err.println("v" + v);
+		if (v == null) {
+			return "Repository location is mandatory";
+		}
+
+		// remove unneeded whitespaces
+		v = v.trim();
+		// check if the repository location has been prefixed
+		if (!(v.startsWith("http://") || v.startsWith("https://")
+				|| v.startsWith("svn://") || v.startsWith("svn+ssh://") || v
+				.startsWith("file://"))) {
+			return "incorrect Repository URL. See "
+					+ "<a href=\"http://svnbook.red-bean.com/en/1.2/svn-book.html#svn.basic.in-action.wc.tbl-1\">this</a> "
+					+ "for information about valid URLs.";
+		}
+		return null;
+	}
+
+	/**
+	 * this method does basically the same as
+	 * {@link #validateRemoteLocation(String)} does. The difference is that it
+	 * will check the local location instead of the remote one
+	 * 
+	 * @see #validateRemoteLocation(String)
+	 */
+	private static String validateLocalLocation(String localLocation) {
+		String v = Util.nullify(localLocation);
+		if (v == null) {
+			// local directory is optional so this is ok
+			return null;
+		}
+
+		v = v.trim();
+
+		// check if a absolute path has been supplied
+		// (the last check with the regex will match windows drives)
+		if (v.startsWith("/") || v.startsWith("\\") || v.startsWith("..")
+				|| v.matches("^[A-Za-z]:")) {
+			return "neither absolute nor relative paths are allowed";
+		}
+
+		// all tests passed so far
+		return null;
+	}
+
+	public boolean update(Launcher launcher, FilePath workspace,
+			final TaskListener listener) throws IOException,
+			InterruptedException {
+		final ISVNAuthenticationProvider authProvider = getDescriptor()
+				.createAuthenticationProvider();
+		return workspace.act(new FileCallable<Boolean>() {
+			public Boolean invoke(File ws, VirtualChannel channel)
+					throws IOException {
+				SVNUpdateClient svnuc = createSvnClientManager(authProvider)
+						.getUpdateClient();
+				svnuc
+						.setEventHandler(new SubversionUpdateEventHandler(
+								listener));
+
+				for (ModuleLocation l : getLocations()) {
+					if (l.getErrorMessage() != null) {
+						listener.fatalError("Unable to perform update: "
+								+ l.getErrorMessage()
+								+ ". Please validate your configuration.");
+						return false;
+					}
+					try {
+						String url = l.getRemote();
+						listener.getLogger().println("Updating " + url);
+						svnuc.doUpdate(new File(ws, l.getLocal()),
+								SVNRevision.HEAD, true);
+					} catch (SVNException e) {
+						e
+								.printStackTrace(listener
+										.error("Error in subversion"));
+						return false;
+					}
+				}
+				return true;
+			}
+		});
+	}
+
+	/**
+	 * Returns true if we can use "svn update" instead of "svn checkout"
+	 */
+	private boolean isUpdatable(FilePath workspace, final TaskListener listener)
+			throws IOException, InterruptedException {
+		final ISVNAuthenticationProvider authProvider = getDescriptor()
+				.createAuthenticationProvider();
+
+		return workspace.act(new FileCallable<Boolean>() {
+			public Boolean invoke(File ws, VirtualChannel channel)
+					throws IOException {
+
+				for (ModuleLocation l : getLocations()) {
+					String url = l.getRemote();
+					String moduleName = l.getLocal();
+					File module = new File(ws, moduleName);
+
+					if (!module.exists()) {
+						listener.getLogger().println(
+								"Checking out a fresh workspace because "
+										+ module + " doesn't exist");
+						return false;
+					}
+
+					try {
+						SvnInfo svnInfo = new SvnInfo(parseSvnInfo(module,
+								authProvider));
+						if (!svnInfo.url.equals(url)) {
+							listener.getLogger().println(
+									"Checking out a fresh workspace because the workspace is not "
+											+ url);
+							return false;
+						}
+					} catch (SVNException e) {
+						listener
+								.getLogger()
+								.println(
+										"Checking out a fresh workspace because Hudson failed to detect the current workspace "
+												+ module);
+						e.printStackTrace(listener.error(e.getMessage()));
+						return false;
+					}
+				}
+				return true;
+			}
+		});
+	}
+
+	public boolean pollChanges(AbstractProject project, Launcher launcher,
+			FilePath workspace, TaskListener listener) throws IOException,
+			InterruptedException {
+		// current workspace revision
+		Map<String, SvnInfo> wsRev = buildRevisionMap(workspace, listener);
+
+		ISVNAuthenticationProvider authProvider = getDescriptor()
+				.createAuthenticationProvider();
+
+		// check the corresponding remote revision
+		for (SvnInfo localInfo : wsRev.values()) {
+			try {
+				SvnInfo remoteInfo = new SvnInfo(parseSvnInfo(localInfo
+						.getSVNURL(), authProvider));
+				listener.getLogger().println("Revision:" + remoteInfo.revision);
+				if (remoteInfo.revision > localInfo.revision)
+					return true; // change found
+			} catch (SVNException e) {
+				e.printStackTrace(listener
+						.error("Failed to check repository revision for "
+								+ localInfo.url));
+			}
+		}
+
+		return false; // no change
+	}
+
+	public ChangeLogParser createChangeLogParser() {
+		return new SubversionChangeLogParser();
+	}
+
+	public DescriptorImpl getDescriptor() {
+		return DescriptorImpl.DESCRIPTOR;
+	}
+
+	public void buildEnvVars(Map<String, String> env) {
+		// no environment variable
+	}
+
+	public FilePath getModuleRoot(FilePath workspace) {
+		if (getLocations().length > 0)
+			return workspace.child(getLocations()[0].getLocal());
+		return workspace;
+	}
+
+	private static String getLastPathComponent(String s) {
+		String[] tokens = s.split("/");
+		return tokens[tokens.length - 1]; // return the last token
+	}
+
+	public static final class DescriptorImpl extends Descriptor<SCM> {
+		public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
+
+		/**
+		 * Path to <tt>svn.exe</tt>. Null to default.
+		 * 
+		 * @deprecated No longer in use.
+		 */
+		private volatile String svnExe;
+
+		/**
+		 * SVN authentication realm to its associated credentials.
+		 */
+		private final Map<String, Credential> credentials = new Hashtable<String, Credential>();
+
+		/**
+		 * Stores {@link SVNAuthentication} for a single realm.
+		 */
+		private static abstract class Credential implements Serializable {
+			abstract SVNAuthentication createSVNAuthentication();
+		}
+
+		private static final class PasswordCredential extends Credential {
+			private final String userName;
+
+			private final String password; // scrambled by base64
+
+			public PasswordCredential(String userName, String password) {
+				this.userName = userName;
+				this.password = Scrambler.scramble(password);
+			}
+
+			@Override
+			SVNPasswordAuthentication createSVNAuthentication() {
+				return new SVNPasswordAuthentication(userName, Scrambler
+						.descramble(password), false);
+			}
+		}
+
+		/**
+		 * Remoting interface that allows remote
+		 * {@link ISVNAuthenticationProvider} to read from local
+		 * {@link DescriptorImpl#credentials}.
+		 */
+		private interface RemotableSVNAuthenticationProvider {
+			Credential getCredential(String realm);
+		}
+
+		private final class RemotableSVNAuthenticationProviderImpl implements
+				RemotableSVNAuthenticationProvider, Serializable {
+			public Credential getCredential(String realm) {
+				return credentials.get(realm);
+			}
+
+			/**
+			 * When sent to the remote node, send a proxy.
+			 */
+			private Object writeReplace() {
+				return Channel.current().export(
+						RemotableSVNAuthenticationProvider.class, this);
+			}
+		}
+
+		/**
+		 * See {@link DescriptorImpl#createAuthenticationProvider()}.
+		 */
+		private static final class SVNAuthenticationProviderImpl implements
+				ISVNAuthenticationProvider, Serializable {
+			private final RemotableSVNAuthenticationProvider source;
+
+			public SVNAuthenticationProviderImpl(
+					RemotableSVNAuthenticationProvider source) {
+				this.source = source;
+			}
+
+			public SVNAuthentication requestClientAuthentication(String kind,
+					SVNURL url, String realm, SVNErrorMessage errorMessage,
+					SVNAuthentication previousAuth, boolean authMayBeStored) {
+				Credential cred = source.getCredential(realm);
+				if (cred == null)
+					return null;
+				return cred.createSVNAuthentication();
+			}
+
+			public int acceptServerAuthentication(SVNURL url, String realm,
+					Object certificate, boolean resultMayBeStored) {
+				return ACCEPTED_TEMPORARY;
+			}
+
+			private static final long serialVersionUID = 1L;
+		}
+
+		private DescriptorImpl() {
+			super(SubversionSCM.class);
+			load();
+		}
+
+		public String getDisplayName() {
+			return "Subversion";
+		}
+
+		public SCM newInstance(StaplerRequest req) {
+			return new SubversionSCM(req.getParameterValues("location_remote"),
+					req.getParameterValues("location_local"), req
+							.getParameter("svn_use_update") != null, req
+							.getParameter("svn_username"), req
+							.getParameter("svn_other_options"));
+		}
+
+		/**
+		 * Creates {@link ISVNAuthenticationProvider} backed by
+		 * {@link #credentials}. This method must be invoked on the master, but
+		 * the returned object is remotable.
+		 */
+		public ISVNAuthenticationProvider createAuthenticationProvider() {
+			return new SVNAuthenticationProviderImpl(
+					new RemotableSVNAuthenticationProviderImpl());
+		}
+
+		/**
+		 * Used in the job configuration page to check if authentication for the
+		 * SVN URLs are available.
+		 */
+		public void doAuthenticationCheck(final StaplerRequest req,
+				StaplerResponse rsp) throws IOException, ServletException {
+			new FormFieldValidator(req, rsp, true) {
+				protected void check() throws IOException, ServletException {
+					StringTokenizer tokens = new StringTokenizer(
+							fixNull(request.getParameter("value")));
+					String message = "";
+
+					while (tokens.hasMoreTokens()) {
+						String url = tokens.nextToken();
+
+						try {
+							SVNRepository repository = SVNRepositoryFactory
+									.create(SVNURL.parseURIDecoded(url));
+
+							ISVNAuthenticationManager sam = SVNWCUtil
+									.createDefaultAuthenticationManager();
+							sam
+									.setAuthenticationProvider(createAuthenticationProvider());
+							repository.setAuthenticationManager(sam);
+
+							repository.testConnection();
+						} catch (SVNException e) {
+							StringWriter sw = new StringWriter();
+							e.printStackTrace(new PrintWriter(sw));
+
+							message += "Unable to access "
+									+ url
+									+ " : "
+									+ Util.escape(e.getErrorMessage()
+											.getFullMessage());
+							message += " <a href='#' id=svnerrorlink onclick='javascript:"
+									+ "document.getElementById(\"svnerror\").style.display=\"block\";"
+									+ "document.getElementById(\"svnerrorlink\").style.display=\"none\";"
+									+ "return false;'>(show details)</a>";
+							message += "<pre id=svnerror style='display:none'>"
+									+ sw + "</pre>";
+							message += " (Maybe you need to <a href='"
+									+ req.getContextPath()
+									+ "/scm/SubversionSCM/enterCredential?"
+									+ url + "'>enter credential</a>?)";
+							message += "<br>";
+							logger.log(Level.INFO,
+									"Failed to access subversion repository "
+											+ url, e);
+						}
+					}
+
+					if (message.length() == 0)
+						ok();
+					else
+						error(message);
+				}
+			}.process();
+		}
+
+		/**
+		 * Submits the authentication info.
+		 */
+		public void doPostCredential(final StaplerRequest req,
+				StaplerResponse rsp) throws IOException, ServletException {
+			final String url = req.getParameter("url");
+			final String username = req.getParameter("username");
+			final String password = req.getParameter("password");
+
+			try {
+				// the way it works with SVNKit is that
+				// 1) svnkit calls AuthenticationManager asking for a
+				// credential.
+				// this is when we can see the 'realm', which identifies the
+				// user domain.
+				// 2) DefaultSVNAuthenticationManager returns the username and
+				// password we set below
+				// 3) if the authentication is successful, svnkit calls back
+				// acknowledgeAuthentication
+				// (so we store the password info here)
+				SVNRepository repository = SVNRepositoryFactory.create(SVNURL
+						.parseURIDecoded(url));
+				repository
+						.setAuthenticationManager(new DefaultSVNAuthenticationManager(
+								SVNWCUtil.getDefaultConfigurationDirectory(),
+								true, username, password) {
+							public void acknowledgeAuthentication(
+									boolean accepted, String kind,
+									String realm, SVNErrorMessage errorMessage,
+									SVNAuthentication authentication)
+									throws SVNException {
+								if (accepted) {
+									credentials.put(realm,
+											new PasswordCredential(username,
+													password));
+									save();
+								}
+								super.acknowledgeAuthentication(accepted, kind,
+										realm, errorMessage, authentication);
+							}
+						});
+				repository.testConnection();
+				rsp.sendRedirect("credentialOK");
+			} catch (SVNException e) {
+				req.setAttribute("message", e.getErrorMessage());
+				rsp.forward(Hudson.getInstance(), "error", req);
+			}
+		}
+
+		/**
+		 * validate the value for a remote (repository) location.
+		 * 
+		 * @param req
+		 * @param rsp
+		 * @throws IOException
+		 * @throws ServletException
+		 * @since 1.64
+		 */
+		public void doSvnRemoteLocationCheck(StaplerRequest req,
+				StaplerResponse rsp) throws IOException, ServletException {
+			new FormFieldValidator(req, rsp, false) {
+				protected void check() throws IOException, ServletException {
+					String retMessage = validateRemoteLocation(request
+							.getParameter("value"));
+
+					if (retMessage != null) {
+						// validation failed. the errormessage is the string
+						// that is beeing returned
+						error(retMessage);
+						return;
+					}
+					// validation has been successful
+					ok();
+				}
+			}.process();
+		}
+
+		/**
+		 * validate the value for a local location (local checkout directory).
+		 * 
+		 * @param req
+		 * @param rsp
+		 * @throws IOException
+		 * @throws ServletException
+		 * @since 1.64
+		 */
+		public void doSvnLocalLocationCheck(StaplerRequest req,
+				StaplerResponse rsp) throws IOException, ServletException {
+			new FormFieldValidator(req, rsp, false) {
+				protected void check() throws IOException, ServletException {
+					String retMessage = validateLocalLocation(request
+							.getParameter("value"));
+					if (retMessage != null) {
+						// validation failed. the errormessage is the string
+						// that is beeing returned
+						error(retMessage);
+						return;
+					}
+				}
+			}.process();
+		}
+
+		static {
+			new Initializer();
+		}
+	}
+
+	private static final long serialVersionUID = 1L;
+
+	private static final Logger logger = Logger.getLogger(SubversionSCM.class
+			.getName());
+
+	private static final LocatorImpl DUMMY_LOCATOR = new LocatorImpl();
+
+	static {
+		new Initializer();
+		DUMMY_LOCATOR.setLineNumber(-1);
+		DUMMY_LOCATOR.setColumnNumber(-1);
+	}
+
+	private static final class Initializer {
+		static {
+			DAVRepositoryFactory.setup(); // http, https
+			SVNRepositoryFactoryImpl.setup(); // svn, svn+xxx
+			FSRepositoryFactory.setup(); // file
+		}
+	}
+
+	/**
+	 * small structure to store local and remote (repository) location
+	 * information of the repository. As a addition it holds the invalid field
+	 * to make failure messages when doing a checkout possible
+	 */
+	public static final class ModuleLocation {
+		private String remote;
+
+		private String local;
+
+		private String errorMessage;
+
+		public ModuleLocation(String remote, String local) {
+			// null indicates that there is no error with this location
+			this(remote, local, null);
+		}
+
+		public ModuleLocation(String remote, String local, String errorMessage) {
+			this.remote = remote;
+			this.local = local;
+			this.errorMessage = errorMessage;
+		}
+
+		public String getLocal() {
+			return local;
+		}
+
+		public String getRemote() {
+			return remote;
+		}
+
+		public String getErrorMessage() {
+			return errorMessage;
+		}
+	}
+
 }