Subversion adapter patch

Jim Hague <[email protected]> Wed, 25 Jan 2006 19:46:59 +0000
Newsgroups gmane.comp.java.anthill.devel
Message-ID <[email protected]>
--Boundary-00=_zW91Dy6DEbgnpFo
Content-Type: text/plain;
  charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Attached is a patch for the Subversion adapter. It fixes two problems:

1. After a successful build, the Subversion adapter in CVS tags the
   source tree at the point it was at when the build completed. If any
   checkin has happened during the checkout/build, it will be included
   in the tag despite it not being in the build.

   The fix (which I have submitted before but hasn't made it into CVS)
   is to grab the revision number of the checkout at checkout, and tag
   at that revision.

2. Subversion projects will repeat a successful build unnecessarily.
   When collecting changes since the last successful build, the adapter
   executes 'svn log -r {date of last build}:HEAD'. I'd expected
   Subversion to list changes since the given date. Actually it lists
   revisions starting with the revision current at the given date.
   After a successful build the date is advanced to the time of the
   start of the successful build, but on the next build cycle the
   revision triggering the first build is still the revision
   current at the time of last successful build. It's only after
   a second successful build that the time moves to a point after
   the next repository change, the incrementing of the build number
   after the first build. The fix is simple; when collecting revisions,
   ignore any from before the last successful build date.

There is one other minor change. If running on 1.4 or greater I try to parse
the Subversion timezone. Oh, and fixing what looks to me like indentation
wrongness.

Finally, please could someone fix the bad line endings at the start of
AnthillFlushQueueServlet.java.
-- 
Jim Hague - [email protected]          Never trust a computer you can't lift.

--Boundary-00=_zW91Dy6DEbgnpFo
Content-Type: text/x-diff;
  charset="us-ascii";
  name="Anthill.patch.svn2"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filename="Anthill.patch.svn2"

--- ../Anthill-vendor/build/build.xml	2006-01-24 22:09:21.000000000 +0000
+++ build/build.xml	2006-01-25 12:25:27.000000000 +0000
@@ -152,6 +152,7 @@
     <fixcrlf srcdir="${conf.profile.dir}/Unix/unix_cvs" />
     <fixcrlf srcdir="${conf.profile.dir}/Unix/unix_perforce" />
     <fixcrlf srcdir="${conf.profile.dir}/Unix/unix_pvcs" />
+    <fixcrlf srcdir="${conf.profile.dir}/Unix/unix_subversion" />
     <fixcrlf srcdir="${conf.profile.dir}/Unix/unix_vss" />
     <fixcrlf srcdir="${build.dir}" excludes="**/CVS **/*.class" />
   </target>
--- ../Anthill-vendor/source/main/java/com/urbancode/anthill/adapter/SubversionRepositoryAdapter.java	2006-01-24 22:09:23.000000000 +0000
+++ source/main/java/com/urbancode/anthill/adapter/SubversionRepositoryAdapter.java	2006-01-25 13:08:22.000000000 +0000
@@ -20,6 +20,7 @@
 
 import org.apache.log4j.Logger;
 import com.urbancode.anthill.util.StreamPumper;
+import com.urbancode.anthill.BuildDefinition;
 import java.io.*;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -77,18 +78,37 @@
     public static final String BUILD_INCREMENT_COMMENT = "Anthill: Increment build number";
     
     // date format understood by Subversion commands
-    public static SimpleDateFormat SUBVERSION_DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
+    public static String SUBVERSION_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss ZZZZZ";
+    public static String SUBVERSION_DATE_FORMAT_PRE1_4 = "yyyy-MM-dd HH:mm:ss";
+    public static SimpleDateFormat SUBVERSION_DATE;
+    
     // The file delimiting token in log output.
     private static final String REV_DELIM_TOKEN = "------------------------------------------------------------------------";
     
     private static final String NEW_LINE = System.getProperty("line.separator");
+
+    static 
+    {
+        // If we're 1.4 or greater, observe the timezone in the Subversion
+        // date strings. Otherwise ignore it and hope it matches our timezone.
+        String javaver = System.getProperty("java.version");
+        char major = javaver.charAt(0);
+        char dot = javaver.charAt(1);
+        char minor = javaver.charAt(2);
+        String fmt = SUBVERSION_DATE_FORMAT;
+
+        if ( major == '1' && dot == '.' && minor <= '3' )
+            fmt = SUBVERSION_DATE_FORMAT_PRE1_4;
+
+        SUBVERSION_DATE = new SimpleDateFormat(fmt);
+    }
     
-    
-	//*************************************************************************
-	// Instance
-	//*************************************************************************
-    
+    //*************************************************************************	   // Instance
+    //*************************************************************************
+	
+    // The revision we are building.
+    protected String buildRevision;
+
     /**
      * Create a new SubversionRepositoryAdapter
      */
@@ -107,6 +127,99 @@
     }
     
     /**
+     * checks out entire project and notes the revision the local
+     * copy is at for when it is time to tag later on.
+     */
+    public void getWorkingProjectCopy(BuildDefinition def)
+	throws RepositoryException {
+	super.getWorkingProjectCopy(def);
+	
+        log.debug("Getting working copy of project: " + project.getProjectName());
+        Process p = null;
+        StreamPumper errorPumper = null;
+        int exitcode = 0;
+        try {
+            Map tempMap = new HashMap();
+            tempMap.put("Adapter", this);
+            tempMap.put("Properties", project.getProperties());
+            if (def.getVersionedBuildFlag()){
+                log.info("Retrieving project version " + def.getVersion());
+                tempMap.put("Version", def.getVersion().trim());
+            }
+            
+            Pagelet pagelet = getPageletFactory().getPagelet(makeProfilePageletName(WORKING_PROJECT_PAGELET));
+            if (pagelet != null) {
+                log.debug("Have checkout pagelet.");
+            }
+            else {
+                log.debug("Pagelet is null in checkout!");
+            }
+            String commandString = pagelet.service(tempMap);
+            log.debug("Checkout Command: " + commandString);
+            p = Runtime.getRuntime().exec(toArray(commandString));
+
+            // pump the error stream.
+            errorPumper = new StreamPumper(p.getErrorStream(), "getRevisions",
+            System.err, true);
+            errorPumper.start();
+            
+            // get and parse the input stream
+            parseCheckoutCommandResult(p.getInputStream());
+            
+            exitcode = p.waitFor();
+        }
+        catch (RepositoryException e) {
+            throw e;
+        }
+        catch (Exception e) {
+            throw new RepositoryException(
+            "Checkout failed: " + e.getMessage(),
+            e);
+        }
+        finally {
+            if (errorPumper != null) {
+                try {
+                    errorPumper.join();
+                } catch (InterruptedException e) {
+                    throw new RepositoryException(e);
+                }
+            }
+        }
+        // handle errors
+        if (exitcode != 0)
+            throw (new RepositoryException("svn checkout failed.  Exit code: " + exitcode));
+    }
+    
+    protected void parseCheckoutCommandResult(InputStream in)
+	throws IOException, RepositoryException {
+	BufferedReader br = new BufferedReader(new InputStreamReader(in));
+	RE checkedOutRE = null;
+
+	// The last line says "Checked out revision nnn."
+	// Start by collecting the last line.
+	String lastLine = null;
+	for ( String line = br.readLine(); line != null; line = br.readLine() )
+	    lastLine = line;
+
+	try
+	{
+	    checkedOutRE = new RE("(\\d+)");
+	}
+	catch (RESyntaxException rse)
+	{
+	    log.error(rse);
+	    throw new RepositoryException(rse);
+	}
+
+	if ( !checkedOutRE.match(lastLine) )
+	{
+	    log.error("No revision number in last line of checkout" + lastLine);
+	    throw new RepositoryException("No revision number in " + lastLine);
+	}
+	buildRevision = checkedOutRE.getParen(1);
+    }
+
+    /**
      * Returns a List of Revision objects detailing the changes that have
      * been made since the specified date.
      * <p>
@@ -131,7 +244,7 @@
             tempMap.put("Date", date);
             Pagelet pagelet = getPageletFactory().getPagelet(makeProfilePageletName(GET_REVISIONS_SINCE_PAGELET));
             String commandString = pagelet.service(tempMap);
-            log.info("Get revisions since command: " + commandString);
+            log.debug("Get revisions since command: " + commandString);
             p = Runtime.getRuntime().exec(toArray(commandString));
 
             // pump the error stream.
@@ -141,7 +254,7 @@
             
             // get and parse the input stream
             InputStream input = p.getInputStream();
-            parseLogCommandResult(input, revisionList);
+            parseLogCommandResult(input, revisionList, date);
             
             exitcode = p.waitFor();
             
@@ -169,7 +282,24 @@
         return revisionList;
     }
     
-    protected void parseLogCommandResult(InputStream in, List revList)
+    /**
+     * Parse the output of 'svn log'. Build a new ChangesetRevision for
+     * each revision we find. When each ChangesetRevision is complete,
+     * add it to the list of revisions UNLESS it is either a change
+     * incrementing the build number file (these will have the build
+     * increment comment) OR its date precedes the date we're starting
+     * at. It's a curiosity of Subversion that dates always map to a
+     * revision; therefore, the start date will be mapped to the
+     * first revision *prior* to the date (as of svn 1.2.3, at least)
+     * and there is no way to explain you don't want that. So filter
+     * out by hand here.
+     *
+     * @param in        the input stream to read.
+     * @param revList   the revision list we're building.
+     * @param firstDate the starting date.
+     */
+    protected void parseLogCommandResult(InputStream in, List revList,
+                                         Date firstDate)
 	throws IOException, RepositoryException {
 	final int PARSE_REV_START = 1;
 	final int PARSE_REV_FILELIST = 2;
@@ -181,7 +311,7 @@
 	RE headerRE = null;
 	RE fileRE = null;
 	int parseState = PARSE_REV_START;
-	
+
 	try
 	{
 	    headerRE = new RE("^r(\\d+) \\| (\\S+) \\| ([^\\(]+)");
@@ -228,35 +358,34 @@
 		// Grab all file info lines, ignoring any that doesn't
 		// match their pattern. The end of this section is
 		// indicated by a blank line.
-        //log.debug("Parsing revision file list. Line: " + line);
-        if (fileRE.match(line)) {
-            char ftype = fileRE.getParen(1).charAt(0);
-            log.debug("Change Char Found: " + ftype);
-            Revision fileRevision = new Revision();
-
-            fileRevision.fileName = fileRE.getParen(2);
-            switch (ftype) {
-                case 'A' :
-                    rev.addAddedFile(fileRevision);
-                    break;
-
-                case 'D' :
-                    rev.addDeletedFile(fileRevision);
-                    break;
-
-                case 'M' :
-                    rev.addModifiedFile(fileRevision);
-                    break;
-
-                case 'R' :
-                    rev.addReplacedFile(fileRevision);
-                    break;
+                if (fileRE.match(line)) {
+                    char ftype = fileRE.getParen(1).charAt(0);
+                    log.debug("Change Char Found: " + ftype);
+                    Revision fileRevision = new Revision();
+
+                    fileRevision.fileName = fileRE.getParen(2);
+                    switch (ftype) {
+                    case 'A' :
+                        rev.addAddedFile(fileRevision);
+                        break;
+
+                    case 'D' :
+                        rev.addDeletedFile(fileRevision);
+                        break;
+
+                    case 'M' :
+                        rev.addModifiedFile(fileRevision);
+                        break;
+
+                    case 'R' :
+                        rev.addReplacedFile(fileRevision);
+                        break;
 
-            }
-        }
+                    }
+                }
 		else if (line.trim().length() == 0 ) {
 		    parseState = PARSE_REV_COMMENT;
-        }
+                }
 		break;
 
 	    case PARSE_REV_COMMENT:
@@ -265,10 +394,13 @@
 		{
 		    // End of revision info. Add the current revision
 		    // provided that the revision is not a build
-		    // version increment.
-		    if ( comment.toString().lastIndexOf(BUILD_INCREMENT_COMMENT) < 0 ) {
-			     rev.comment = comment.toString();
-			     revList.add(rev);
+		    // version increment and doesn't predate the
+                    // the starting date.
+		    if ( rev != null &&
+		    	 (firstDate == null || rev.date == null || !rev.date.before(firstDate)) &&
+                         comment.toString().lastIndexOf(BUILD_INCREMENT_COMMENT) < 0 ) {
+                        rev.comment = comment.toString();
+                        revList.add(rev);
 		    }
 
 		    // Empty the comment buffer and reset the revision
@@ -297,5 +429,41 @@
      * @param file  file to prepare for editing
      */
     public void prepareFileForEdit(String file) throws RepositoryException {
-    }    
+    }
+
+    /**
+     * Labels all relevant project files with the provided tag.
+     * A Subversion label/tag operation is simply a server-side copy of
+     * the project, but that means we must make sure we copy the project
+     * at the revision at which we built it. So we pass
+     * <code>buildRevision</code> down to the labelling code.
+     *
+     * @param tag The label to tag the files with
+     */
+    public void label(String tag) throws RepositoryException {
+        if (tag == null || tag.length() == 0) {
+            throw (new RepositoryException("No label specified"));
+        }
+
+        tag = tag.replace('$', '_').replace(',', '_').replace('.', '_')
+                 .replace(':', '_').replace(';', '_').replace('@', '_');
+
+        log.info("Tagging entire project with label: " + tag);
+
+        try {
+            Map tempMap = new HashMap();
+            tempMap.put("Properties", project.getProperties());
+            tempMap.put("Adapter", this);
+            tempMap.put("Tag", tag);
+            tempMap.put("BuildRevision", buildRevision);
+            Pagelet pagelet = getPageletFactory().getPagelet(makeProfilePageletName(LABEL_PAGELET));
+            executeCommand(pagelet.service(tempMap), "Label");
+        }
+        catch (RepositoryException e) {
+            throw e;
+        }
+        catch (Exception e) {
+            throw new RepositoryException("Label failed: " + e.getMessage(), e);
+        }
+    }
 }
--- ../Anthill-vendor/source/main/profiles/Win32/win_subversion/label.pgl	2006-01-24 22:09:23.000000000 +0000
+++ source/main/profiles/Win32/win_subversion/label.pgl	2004-11-08 10:03:42.000000000 +0000
@@ -7,6 +7,7 @@
 ProfileRepositoryAdapter ra = (ProfileRepositoryAdapter)context.get("Adapter");
 ProjectProperties pp = (ProjectProperties)context.get("Properties");
 String tag = (String)context.get("Tag");
+String buildRevision = (String)context.get("BuildRevision");
 String url = pp.getProperty(SubversionRepositoryAdapter.URL_KEY);
 String tagUrl = pp.getProperty(SubversionRepositoryAdapter.TAGS_URL_KEY);
 String user = pp.getProperty(SubversionRepositoryAdapter.USER_KEY).trim();
@@ -25,4 +26,4 @@
 
 %>
 
-svn copy --non-interactive -m "Copied by Anthill" <%=authArgs%> <%=url%> <%=tagUrl%>
+svn copy --non-interactive -m "Copied by Anthill" <%=authArgs%> -r <%=buildRevision%> <%=url%> <%=tagUrl%>
--- ../Anthill-vendor/source/main/profiles/Unix/unix_subversion/label.pgl	2006-01-24 22:09:23.000000000 +0000
+++ source/main/profiles/Unix/unix_subversion/label.pgl	2004-11-08 10:03:16.000000000 +0000
@@ -7,6 +7,7 @@
 ProfileRepositoryAdapter ra = (ProfileRepositoryAdapter)context.get("Adapter");
 ProjectProperties pp = (ProjectProperties)context.get("Properties");
 String tag = (String)context.get("Tag");
+String buildRevision = (String)context.get("BuildRevision");
 String url = pp.getProperty(SubversionRepositoryAdapter.URL_KEY);
 String tagUrl = pp.getProperty(SubversionRepositoryAdapter.TAGS_URL_KEY);
 String user = pp.getProperty(SubversionRepositoryAdapter.USER_KEY).trim();
@@ -29,4 +30,4 @@
 
 %>
 
-sh <%=pageletDir%>label.sh <%=authArgs%> <%=url%> <%=tagUrl%>
+sh <%=pageletDir%>label.sh <%=authArgs%> -r <%=buildRevision%> <%=url%> <%=tagUrl%>

--Boundary-00=_zW91Dy6DEbgnpFo
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
Anthill-dev mailing list
Anthill-dev-IWHQxnLZ/[email protected]
http://lists.urbancode.com/mailman/listinfo/anthill-dev

--Boundary-00=_zW91Dy6DEbgnpFo--