docs: def parse(self, m, prefix=None):
          """Parse messages sent by the 'buildbot-cvs-mail' program.
          """
          # 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 = parseaddr(m["from"])
          if not addr:
              # no From means this message isn't from buildbot-cvs-mail
              return None
          at = addr.find("@")
          if at == -1:
              author = addr  # might still be useful
          else:
              author = addr[:at]
          author = util.bytes2unicode(author, encoding="ascii")
  
          # CVS accepts RFC822 dates. buildbot-cvs-mail adds the date as
          # part of the mail header, so use that.
          # This assumes cvs is being access via ssh or pserver, so the time
          # will be the CVS server's time.
  
          # calculate a "revision" based on that timestamp, or the current time
          # if we're unable to parse the date.
          log.msg('Processing CVS mail')
          dateTuple = parsedate_tz(m["date"])
          if dateTuple is None:
              when = util.now()
          else:
              when = mktime_tz(dateTuple)
  
          theTime = datetime.datetime.utcfromtimestamp(float(when))
          rev = theTime.strftime('%Y-%m-%d %H:%M:%S')
  
          catRE = re.compile(r'^Category:\s*(\S.*)')
          cvsRE = re.compile(r'^CVSROOT:\s*(\S.*)')
          cvsmodeRE = re.compile(r'^Cvsmode:\s*(\S.*)')
          filesRE = re.compile(r'^Files:\s*(\S.*)')
          modRE = re.compile(r'^Module:\s*(\S.*)')
          pathRE = re.compile(r'^Path:\s*(\S.*)')
          projRE = re.compile(r'^Project:\s*(\S.*)')
          singleFileRE = re.compile(r'(.*) (NONE|\d(\.|\d)+) (NONE|\d(\.|\d)+)')
          tagRE = re.compile(r'^\s+Tag:\s*(\S.*)')
          updateRE = re.compile(r'^Update of:\s*(\S.*)')
          comments = ""
          branch = None
          cvsroot = None
          fileList = None
          files = []
          isdir = 0
          path = None
          project = None
  
          lines = list(body_line_iterator(m))
          while lines:
              line = lines.pop(0)
              m = catRE.match(line)
              if m:
                  category = m.group(1)
                  continue
              m = cvsRE.match(line)
              if m:
                  cvsroot = m.group(1)
                  continue
              m = cvsmodeRE.match(line)
              if m:
                  cvsmode = m.group(1)
                  continue
              m = filesRE.match(line)
              if m:
                  fileList = m.group(1)
                  continue
              m = modRE.match(line)
              if m:
                  # We don't actually use this
                  # module = m.group(1)
                  continue
              m = pathRE.match(line)
              if m:
                  path = m.group(1)
                  continue
              m = projRE.match(line)
              if m:
                  project = m.group(1)
                  continue
              m = tagRE.match(line)
              if m:
                  branch = m.group(1)
                  continue
              m = updateRE.match(line)
              if m:
                  # We don't actually use this
                  # updateof = m.group(1)
                  continue
              if line == "Log Message:\n":
                  break
  
          # CVS 1.11 lists files as:
          #   repo/path file,old-version,new-version file2,old-version,new-version
          # Version 1.12 lists files as:
          #   file1 old-version new-version file2 old-version new-version
          #
          # files consists of tuples of 'file-name old-version new-version'
          # The versions are either dotted-decimal version numbers, ie 1.1
          # or NONE. New files are of the form 'NONE NUMBER', while removed
          # files are 'NUMBER NONE'. 'NONE' is a literal string
          # Parsing this instead of files list in 'Added File:' etc
          # makes it possible to handle files with embedded spaces, though
          # it could fail if the filename was 'bad 1.1 1.2'
          # For cvs version 1.11, we expect
          #  my_module new_file.c,NONE,1.1
          #  my_module removed.txt,1.2,NONE
          #  my_module modified_file.c,1.1,1.2
          # While cvs version 1.12 gives us
          #  new_file.c NONE 1.1
          #  removed.txt 1.2 NONE
          #  modified_file.c 1.1,1.2
  
          if fileList is None:
              log.msg('CVSMaildirSource Mail with no files. Ignoring')
              return None       # We don't have any files. Email not from CVS
  
          if cvsmode == '1.11':
              # Please, no repo paths with spaces!
              m = re.search('([^ ]*) ', fileList)
              if m:
                  path = m.group(1)
              else:
                  log.msg(
                      'CVSMaildirSource can\'t get path from file list. Ignoring mail')
                  return
              fileList = fileList[len(path):].strip()
              singleFileRE = re.compile(
                  r'(.+?),(NONE|(?:\d+\.(?:\d+\.\d+\.)*\d+)),(NONE|(?:\d+\.(?:\d+\.\d+\.)*\d+))(?: |$)')
          elif cvsmode == '1.12':
              singleFileRE = re.compile(
                  r'(.+?) (NONE|(?:\d+\.(?:\d+\.\d+\.)*\d+)) (NONE|(?:\d+\.(?:\d+\.\d+\.)*\d+))(?: |$)')
              if path is None:
                  raise ValueError(
                      'CVSMaildirSource cvs 1.12 require path. Check cvs loginfo config')
          else:
              raise ValueError(
                  'Expected cvsmode 1.11 or 1.12. got: %s' % cvsmode)
  
          log.msg("CVSMaildirSource processing filelist: %s" % fileList)
          while(fileList):
              m = singleFileRE.match(fileList)
              if m:
                  curFile = path + '/' + m.group(1)
                  files.append(curFile)
                  fileList = fileList[m.end():]
              else:
                  log.msg('CVSMaildirSource no files matched regex. Ignoring')
                  return None   # bail - we couldn't parse the files that changed
          # Now get comments
          while lines:
              line = lines.pop(0)
              comments += line
  
          comments = comments.rstrip() + "\n"
          if comments == '\n':
              comments = None
          return ('cvs', dict(author=author, files=files, comments=comments,
                              isdir=isdir, when=when, branch=branch,
                              revision=rev, category=category,
                              repository=cvsroot, project=project,
                              properties=self.properties))