diff -ru buildbot-0.7.5/buildbot/changes/mail.py buildbot-0.7.5jm/buildbot/changes/mail.py
--- buildbot-0.7.5/buildbot/changes/mail.py	Mon Jan 29 12:59:37 2007
+++ buildbot-0.7.5jm/buildbot/changes/mail.py	Mon Jan 29 15:01:17 2007
@@ -293,8 +293,137 @@
     # return buildbot Change object
     return changes.Change(who, files, comments, when=timestamp, branch=branch)
 
+# svn "commit-email.pl" handler.  The format is very similar to freshcvs mail;
+# here's a sample:
 
+#  From: username [at] apache.org    [slightly obfuscated to avoid spam here]
+#  To: commits [at] spamassassin.apache.org
+#  Subject: svn commit: r105955 - in spamassassin/trunk: . lib/Mail
+#  ...
+#
+#  Author: username
+#  Date: Sat Nov 20 00:17:49 2004      [note: TZ = local tz on server!]
+#  New Revision: 105955
+#
+#  Modified:   [also Removed: and Added:]
+#    [filename]
+#    ...
+#  Log:
+#  [log message]
+#  ...
+#
+#
+#  Modified: spamassassin/trunk/lib/Mail/SpamAssassin.pm
+#  [unified diff]
+#
+#  [end of mail]
+def parseSvnCommitEmail(self, fd, prefix=None, sep="/"):
+    """Parse messages sent by the svn 'commit-email.pl' trigger.
+    """
 
+    m = Message(fd)
+    # The mail is sent from the person doing the checkin. Assume that the
+    # local username is enough to identify them (this assumes a one-server
+    # cvs-over-rsh environment rather than the server-dirs-shared-over-NFS
+    # model)
+    name, addr = m.getaddr("from")
+    if not addr:
+       return None # no From means this message isn't from FreshCVS
+    at = addr.find("@")
+    if at == -1:
+       who = addr # might still be useful
+    else:
+       who = addr[:at]
+
+    # we take the time of receipt as the time of checkin. Not correct (it
+    # depends upon the email latency), but it avoids the out-of-order-changes
+    # issue. Also syncmail doesn't give us anything better to work with,
+    # unless you count pulling the v1-vs-v2 timestamp out of the diffs, which
+    # would be ugly. TODO: Pulling the 'Date:' header from the mail is a
+    # possibility, and email.Utils.parsedate_tz may be useful. It should be
+    # configurable, however, because there are a lot of broken clocks out
+    # there.
+    when = util.now()
+
+    files = []
+    comments = ""
+    isdir = 0
+    lines = m.fp.readlines()
+    rev = None
+    while lines:
+       line = lines.pop(0)
+
+       # "Author: jmason"
+       match = re.search(r"^Author: (\S+)", line)
+       if match:
+          who = match.group(1)
+
+       # "New Revision: 105955"
+       match = re.search(r"^New Revision: (\d+)", line)
+       if match:
+          rev = match.group(1)
+
+       # possible TODO: use "Date: ..." data here instead of time of
+       # commit message receipt, above.   however, this timestamp is
+       # specified *without* a timezone, in the server's local TZ,
+       # so to be accurate buildbot would need a config setting to
+       # specify the source server's expected TZ setting!  messy.
+
+       # this stanza ends with the "Log:"
+       if (line == "Log:\n"):
+           break
+
+    # commit message is terminated by the file-listing section
+    while lines:
+       line = lines.pop(0)
+       if (line == "Modified:\n" or
+           line == "Added:\n" or
+           line == "Removed:\n"):
+           break
+       comments += line
+    comments = comments.rstrip() + "\n"
+
+
+    while lines:
+       line = lines.pop(0)
+       if line == "\n":
+           break
+       if line.find("Modified:\n") == 0:
+           continue            # ignore this line
+       if line.find("Added:\n") == 0:
+           continue            # ignore this line
+       if line.find("Removed:\n") == 0:
+           continue            # ignore this line
+       line = line.lstrip()
+       line = line.rstrip()
+       # note: it doesn't actually make sense to use portable functions
+       # like os.path.join and os.sep, because these filenames all use
+       # separator conventions established by the remote CVS server (which
+       # is probably running on unix), not the local buildmaster system.
+       thesefiles = line.split(" ")
+       for f in thesefiles:
+           if prefix:
+               # insist that the file start with the prefix: we may get
+               # changes we don't care about too
+               # [TODO: this fails if prefix contains >1 level of path!!
+               # e.g. prefix = "spamassassin/trunk" will never match.]
+               bits = f.split(sep)
+               if bits[0] == prefix:
+                   f = sep.join(bits[1:])
+               else:
+                   print "ignored file from svn commit: prefix "+prefix+" does not match "+bits[0]+" in "+f
+                   break
+
+           # TODO: figure out how new directories are described, set .isdir
+           files.append(f)
+
+    if not files:
+       print "no matching files found, ignoring commit"
+       return None
+
+    return changes.Change(who, files, comments, when=when, revision=rev)
+
+
 class MaildirSource(maildirtwisted.MaildirTwisted, base.ChangeSource):
     """This source will watch a maildir that is subscribed to a FreshCVS
     change-announcement mailing list.
