### Eclipse Workspace Patch 1.0
#P hudson-core
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.3
diff -u -r1.3 SubversionSCM.java
--- src/main/java/hudson/scm/SubversionSCM.java	16 Nov 2006 14:34:00 -0000	1.3
+++ src/main/java/hudson/scm/SubversionSCM.java	18 Nov 2006 16:04:29 -0000
@@ -52,38 +52,56 @@
  * @author Kohsuke Kawaguchi
  */
 public class SubversionSCM extends AbstractCVSFamilySCM {
-    private final String modules;
+	private ModuleLocation[] locations = new ModuleLocation[0];
     private boolean useUpdate;
     private String username;
     private String otherOptions;
 
-    SubversionSCM( String modules, boolean useUpdate, String username, String otherOptions ) {
-        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();
+    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++) {
+    			
+    			String rl = nullify(remoteLocations[i]);
+    			String ll = nullify(localLocations[i]);
+    			
+    			if (rl != null) {
+    				rl = rl.trim();
+    				
+    				// FIXME URLs should be checked before
+    				// ignore entry if there is a whitespace
+    				if (rl.indexOf(' ') >= 0) continue;
+    				
+    				if (rl.charAt(rl.length()-1) == '/')
+    					rl.substring(0, rl.length()-1);
+    				
+    				if (ll == null)
+    					ll = getLastPathComponent(rl);
+    				l[i] = new ModuleLocation(rl, ll);
+    			}
+			}
+    		locations = l;
+    	}
+    	
         this.useUpdate = useUpdate;
         this.username = nullify(username);
         this.otherOptions = nullify(otherOptions);
     }
 
+    
     /**
-     * Whitespace-separated list of SVN URLs that represent
-     * modules to be checked out.
+     * list of all configured svn locations
      */
-    public String getModules() {
-        return modules;
-    }
+    public ModuleLocation[] getLocations() {
+		return locations;
+	}
 
-    public boolean isUseUpdate() {
+
+	public boolean isUseUpdate() {
         return useUpdate;
     }
 
@@ -97,9 +115,8 @@
 
     private Collection<String> getModuleDirNames() {
         List<String> dirs = new ArrayList<String>();
-        StringTokenizer tokens = new StringTokenizer(modules);
-        while(tokens.hasMoreTokens()) {
-            dirs.add(getLastPathComponent(tokens.nextToken()));
+        for (ModuleLocation l : locations) {
+        	dirs.add(l.getLocal());
         }
         return dirs;
     }
@@ -187,15 +204,15 @@
                 return false;
         } else {
             workspace.deleteContents();
-            StringTokenizer tokens = new StringTokenizer(modules);
-            while(tokens.hasMoreTokens()) {
+            for (ModuleLocation l : locations) {
                 ArgumentListBuilder cmd = new ArgumentListBuilder();
                 cmd.add(DESCRIPTOR.getSvnExe(),"co","-q","--non-interactive");
                 if(username!=null)
                     cmd.add("--username",username);
                 if(otherOptions!=null)
                     cmd.add(Util.tokenize(otherOptions));
-                cmd.add(tokens.nextToken());
+                cmd.add(l.remote);
+                cmd.add(l.local);
 
                 result = run(launcher,cmd,listener,workspace);
                 if(!result)
@@ -319,7 +336,7 @@
         return new File(build.getRootDir(),"revision.txt");
     }
 
-    public boolean update(Launcher launcher, FilePath remoteDir, BuildListener listener) throws IOException {
+    public boolean update(Launcher launcher, FilePath workspace, BuildListener listener) throws IOException {
         ArgumentListBuilder cmd = new ArgumentListBuilder();
         cmd.add(DESCRIPTOR.getSvnExe(), "update", "-q", "--non-interactive");
 
@@ -328,9 +345,8 @@
         if(otherOptions!=null)
             cmd.add(Util.tokenize(otherOptions));
 
-        StringTokenizer tokens = new StringTokenizer(modules);
-        while(tokens.hasMoreTokens()) {
-            if(!run(launcher,cmd,listener,new FilePath(remoteDir,getLastPathComponent(tokens.nextToken()))))
+        for (ModuleLocation l : locations) {
+            if(!run(launcher,cmd,listener,new FilePath(workspace,l.getLocal())))
                 return false;
         }
         return true;
@@ -340,21 +356,16 @@
      * Returns true if we can use "svn update" instead of "svn checkout"
      */
     private boolean isUpdatable(FilePath workspace,BuildListener listener) {
-        StringTokenizer tokens = new StringTokenizer(modules);
-        while(tokens.hasMoreTokens()) {
-            String url = tokens.nextToken();
-            String moduleName = getLastPathComponent(url);
-            File module = workspace.child(moduleName).getLocal();
-
+    	for (ModuleLocation l : locations) {
             try {
-                SvnInfo svnInfo = SvnInfo.parse(moduleName, createEnvVarMap(false), workspace, listener);
-                if(!svnInfo.url.equals(url)) {
-                    listener.getLogger().println("Checking out a fresh workspace because the workspace is not "+url);
+                SvnInfo svnInfo = SvnInfo.parse(l.getLocal(), createEnvVarMap(false), workspace, listener);
+                if(!svnInfo.url.equals(l.getRemote())) {
+                    listener.getLogger().println("Checking out a fresh workspace because the workspace is not "+l.getRemote());
                     return false;
                 }
             } catch (IOException e) {
-                listener.getLogger().println("Checking out a fresh workspace because Hudson failed to detect the current workspace "+module);
-                e.printStackTrace(listener.error(e.getMessage()));
+				listener.getLogger().println("Checking out a fresh workspace because Hudson failed to detect the current workspace "+ workspace.child(l.getLocal()).getLocal());
+				e.printStackTrace(listener.error(e.getMessage()));
                 return false;
             }
         }
@@ -391,14 +402,7 @@
     }
 
     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));
+        return workspace.child(locations[0].local);
     }
 
     private String getLastPathComponent(String s) {
@@ -428,7 +432,8 @@
 
         public SCM newInstance(StaplerRequest req) {
             return new SubversionSCM(
-                req.getParameter("svn_modules"),
+                req.getParameterValues("location_remote"),
+                req.getParameterValues("location_local"),
                 req.getParameter("svn_use_update")!=null,
                 req.getParameter("svn_username"),
                 req.getParameter("svn_other_options")
@@ -539,6 +544,24 @@
         }
     }
 
+    public static final class ModuleLocation {
+    	private String remote;
+    	private String local;
+		public ModuleLocation(String remote, String local) {
+			super();
+			this.remote = remote;
+			this.local = local;
+		}
+		public String getLocal() {
+			return local;
+		}
+		public String getRemote() {
+			return remote;
+		}
+
+		
+    }
+    
     private static final Pattern SVN_VERSION = Pattern.compile("svn, .+ ([0-9.]+) \\(r([0-9]+)\\)");
 
     private static final Logger LOGGER = Logger.getLogger(SubversionSCM.class.getName());
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.1
diff -u -r1.1 config.jelly
--- src/main/resources/hudson/scm/SubversionSCM/config.jelly	5 Nov 2006 21:14:35 -0000	1.1
+++ src/main/resources/hudson/scm/SubversionSCM/config.jelly	18 Nov 2006 16:04:29 -0000
@@ -1,11 +1,27 @@
 <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.
-    ">
-    <input class="setting-input" name="svn_modules"
-      type="text" value="${scm.modules}"/>
-  </f:entry>
+
+    <f:entry title="Subversion Modules"
+      description="List of Subversion modules which will be used for building the project">
+      <f:repeatable var="loc" items="${scm.locations}">
+        <table width="100%">
+          <f:entry title="Repository location">
+            <input class="setting-input" name="location_remote"
+              type="text" value="${loc.remote}"/>
+          </f:entry>
+
+          <f:entry title="Local module directory (optional)">
+            <input class="setting-input validated" name="location_local"
+              type="text" value="${loc.local}"/>
+          </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.
@@ -30,4 +46,4 @@
         type="text" value="${scm.otherOptions}"/>
     </f:entry>
   </f:advanced>
-</j:jelly>
\ No newline at end of file
+</j:jelly>