[cairo-commit] CVSROOT syncmail,1.4,1.5

Carl Worth cworth at pdx.freedesktop.org
Wed Oct 15 12:53:56 PDT 2003


Update of /cvs/cairo/CVSROOT
In directory pdx:/tmp/cvs-serv2463

Modified Files:
	syncmail 
Log Message:
Switched to a newer(?) version of syncmail snarfed from keithp.com.
This one sets an explicit From address so we can make it static to get past mailman.

Index: syncmail
===================================================================
RCS file: /cvs/cairo/CVSROOT/syncmail,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- syncmail	25 Sep 2003 18:31:14 -0000	1.4
+++ syncmail	15 Oct 2003 19:53:54 -0000	1.5
@@ -1,6 +1,12 @@
 #! /usr/bin/python
-#  -*- Python -*-
- 
+
+# Copyright (c) 2002, Barry Warsaw, Fred Drake, and contributors
+# All rights reserved.
+# See the accompanying LICENSE file for details.
+
+# NOTE: Until SourceForge installs a modern version of Python on the cvs
+# servers, this script MUST be compatible with Python 1.5.2.
+
 """Complicated notification for CVS checkins.
 
 This script is used to provide email notifications of changes to the CVS
@@ -29,15 +35,11 @@
 
     %(PROGRAM)s [options] <%%S> email-addr [email-addr ...]
 
-Where options is:
+Where options are:
 
     --cvsroot=<path>
-    	Use <path> as the environment variable CVSROOT.  Otherwise this
-    	variable must exist in the environment.
-
-    --help
-    -h
-        Print this text.
+        Use <path> as the environment variable CVSROOT.  Otherwise this
+        variable must exist in the environment.
 
     --context=#
     -C #
@@ -46,8 +48,35 @@
     -c
         Produce a context diff (default).
 
+    -m hostname
+    --mailhost hostname
+        The hostname of an available SMTP server.  The default is
+        'localhost'.
+
     -u
-        Produce a unified diff (smaller, but harder to read).
+        Produce a unified diff (smaller).
+
+    -S TEXT
+    --subject-prefix=TEXT
+        Prepend TEXT to the email subject line.
+
+    -R ADDR
+    --reply-to=ADDR
+      Add a "Reply-To: ADDR" header to the email message.
+
+    --quiet / -q
+        Don't print as much status to stdout.
+
+    --fromhost=hostname
+    -f hostname
+        The hostname that email messages appear to be coming from.  The From:
+        header of the outgoing message will look like user at hostname.  By
+        default, hostname is the machine's fully qualified domain name.
+
+    --help / -h
+        Print this text.
+
+The rest of the command line arguments are:
 
     <%%S>
         CVS %%s loginfo expansion.  When invoked by CVS, this will be a single
@@ -58,25 +87,63 @@
 
     email-addrs
         At least one email address.
-
 """
+__version__ = '1.2'
 
 import os
 import sys
-import string
+import re
 import time
+import string
 import getopt
+import smtplib
+import pwd
+import socket
 
-# Notification command
-MAILCMD = '/usr/bin/mail -s "%(SUBJECT)s" %(PEOPLE)s 2>&1 > /dev/null'
+try:
+    from socket import getfqdn
+except ImportError:
+    def getfqdn():
+        # Python 1.5.2 :(
+        hostname = socket.gethostname()
+        byaddr = socket.gethostbyaddr(socket.gethostbyname(hostname))
+        aliases = byaddr[1]
+        aliases.insert(0, byaddr[0])
+        aliases.insert(0, hostname)
+        for fqdn in aliases:
+            if '.' in fqdn:
+                break
+        else:
+            fqdn = 'localhost.localdomain'
+        return fqdn
+
+
+from cStringIO import StringIO
+
+# Which SMTP server to do we connect to?
+MAILHOST = 'localhost'
+MAILPORT = 25
 
 # Diff trimming stuff
 DIFF_HEAD_LINES = 20
 DIFF_TAIL_LINES = 20
 DIFF_TRUNCATE_IF_LARGER = 1000
 
+EMPTYSTRING = ''
+SPACE = ' '
+DOT = '.'
+COMMASPACE = ', '
+
 PROGRAM = sys.argv[0]
 
+BINARY_EXPLANATION_LINES = [
+    "(This appears to be a binary file; contents omitted.)\n"
+    ]
+
+REVCRE = re.compile("^(NONE|[0-9.]+)$")
+NOVERSION = "Couldn't generate diff; no version number found in filespec: %s"
+BACKSLASH = "Couldn't generate diff: backslash in filespec's filename: %s"
+
 
 
 def usage(code, msg=''):
@@ -88,20 +155,60 @@
 
 
 def calculate_diff(filespec, contextlines):
-    try:
-        file, oldrev, newrev = string.split(filespec, ',')
-    except ValueError:
-        # No diff to report
-        return '***** Bogus filespec: %s' % filespec
+    spec = string.split(filespec, ',')
+    if len(spec) < 3:
+        # Too few parts; command line probable used a replacement
+        # other than "%{sVv}"; don't fail, but don't produce a diff
+        # since we can't be sure what diff to generate.
+        return ''
+    # This allows filenames that contain commas:
+    file = string.join(spec[:-2], ",")
+    oldrev = spec[-2]
+    newrev = spec[-1]
+
+    # Make sure we can find a CVS version number
+    if not REVCRE.match(oldrev):
+        return NOVERSION % filespec
+    if not REVCRE.match(newrev):
+        return NOVERSION % filespec
+
+    if string.find(file, '\\') <> -1:
+        # I'm sorry, a file name that contains a backslash is just too much.
+        # XXX if someone wants to figure out how to escape the backslashes in
+        # a safe way to allow filenames containing backslashes, this is the
+        # place to do it.  --Zooko 2002-03-17
+        return BACKSLASH % filespec
+
+    if string.find(file, "'") <> -1:
+        # Those crazy users put single-quotes in their file names!  Now we
+        # have to escape everything that is meaningful inside double-quotes.
+        filestr = string.replace(file, '`', '\`')
+        filestr = string.replace(filestr, '"', '\"')
+        filestr = string.replace(filestr, '$', '\$')
+        # and quote it with double-quotes.
+        filestr = '"' + filestr + '"'
+    else:
+        # quote it with single-quotes.
+        filestr = "'" + file + "'"
     if oldrev == 'NONE':
+        # File is being added.
         try:
             if os.path.exists(file):
                 fp = open(file)
             else:
-                update_cmd = 'cvs -fn update -r %s -p %s' % (newrev, file)
+                update_cmd = "cvs -fn update -r %s -p %s" % (newrev, filestr)
                 fp = os.popen(update_cmd)
             lines = fp.readlines()
             fp.close()
+            # Is this a binary file?  Let's look at the first few
+            # lines to figure it out:
+            for line in lines[:5]:
+                for c in string.rstrip(line):
+                    if c in string.whitespace:
+                        continue
+                    if c < ' ' or c > chr(127):
+                        lines = BINARY_EXPLANATION_LINES[:]
+                        break
             lines.insert(0, '--- NEW FILE: %s ---\n' % file)
         except IOError, e:
             lines = ['***** Error reading new file: ',
@@ -109,59 +216,106 @@
     elif newrev == 'NONE':
         lines = ['--- %s DELETED ---\n' % file]
     else:
+        # File has been changed.
         # This /has/ to happen in the background, otherwise we'll run into CVS
         # lock contention.  What a crock.
         if contextlines > 0:
             difftype = "-C " + str(contextlines)
         else:
             difftype = "-u"
-        diffcmd = "/usr/bin/cvs -f diff -kk %s --minimal -r %s -r %s '%s'" % (
-            difftype, oldrev, newrev, file)
+        diffcmd = "/usr/bin/cvs -f diff -kk %s --minimal -r %s -r %s %s" \
+                  % (difftype, oldrev, newrev, filestr)
         fp = os.popen(diffcmd)
         lines = fp.readlines()
         sts = fp.close()
         # ignore the error code, it always seems to be 1 :(
 ##        if sts:
 ##            return 'Error code %d occurred during diff\n' % (sts >> 8)
-#    if len(lines) > DIFF_TRUNCATE_IF_LARGER:
-#        removedlines = len(lines) - DIFF_HEAD_LINES - DIFF_TAIL_LINES
-#        del lines[DIFF_HEAD_LINES:-DIFF_TAIL_LINES]
-#        lines.insert(DIFF_HEAD_LINES,
-#                     '[...%d lines suppressed...]\n' % removedlines)
+    if len(lines) > DIFF_TRUNCATE_IF_LARGER:
+        removedlines = len(lines) - DIFF_HEAD_LINES - DIFF_TAIL_LINES
+        del lines[DIFF_HEAD_LINES:-DIFF_TAIL_LINES]
+        lines.insert(DIFF_HEAD_LINES,
+                     '[...%d lines suppressed...]\n' % removedlines)
     return string.join(lines, '')
 
 
 
-def blast_mail(mailcmd, filestodiff, contextlines):
+rfc822_specials_re = re.compile(r'[\(\)\<\>\@\,\;\:\\\"\.\[\]]')
+
+def quotename(name):
+    if name and rfc822_specials_re.search(name):
+        return '"%s"' % string.replace(name, '"', '\\"')
+    else:
+        return name
+
+
+
+def blast_mail(subject, people, filestodiff, contextlines, fromhost, replyto):
     # cannot wait for child process or that will cause parent to retain cvs
     # lock for too long.  Urg!
     if not os.fork():
         # in the child
         # give up the lock you cvs thang!
         time.sleep(2)
-        fp = os.popen(mailcmd, 'w')
-        fp.write(sys.stdin.read())
-        fp.write('\n')
-        # append the diffs if available
-        for file in filestodiff:
-            fp.write(calculate_diff(file, contextlines))
-            fp.write('\n')
-        fp.close()
-        # doesn't matter what code we return, it isn't waited on
+        # Create the smtp connection to the localhost
+        conn = smtplib.SMTP()
+        conn.connect(MAILHOST, MAILPORT)
+        user = pwd.getpwuid(os.getuid())[0]
+        name = string.split(pwd.getpwuid(os.getuid())[4], ',')[0]
+        domain = fromhost or getfqdn()
+        address = '%s@%s' % (user, domain)
+        s = StringIO()
+        sys.stdout = s
+        try:
+            vars = {'name'    : quotename(name),
+                    'people'  : string.join(people, COMMASPACE),
+                    'subject' : subject,
+                    'version' : __version__,
+                    'user' : user,
+                    }
+            print '''\
+From: %(name)s <commit at pdx.freedesktop.org>
+To: %(people)s''' % vars
+            if replyto:
+                print 'Reply-To: %s' % replyto
+            print '''\
+Subject: %(subject)s
+X-Mailer: Python syncmail %(version)s <http://sf.net/projects/cvs-syncmail>
+
+Committed by: %(user)s
+''' % vars
+            s.write(sys.stdin.read())
+            # append the diffs if available
+            print
+            for file in filestodiff:
+                print calculate_diff(file, contextlines)
+        finally:
+            sys.stdout = sys.__stdout__
+        resp = conn.sendmail(address, people, s.getvalue())
+        conn.close()
         os._exit(0)
 
 
 
 # scan args for options
 def main():
-    contextlines = 0
+    # XXX Should really move all the options to an object, just to
+    # avoid threading so many positional args through everything.
     try:
-        opts, args = getopt.getopt(sys.argv[1:], 'hC:cu',
-                                   ['context=', 'cvsroot=', 'help'])
+        opts, args = getopt.getopt(
+            sys.argv[1:], 'hC:cuS:R:qf:m:',
+            ['fromhost=', 'context=', 'cvsroot=', 'mailhost=',
+             'subject-prefix=', 'reply-to=',
+             'help', 'quiet'])
     except getopt.error, msg:
         usage(1, msg)
 
     # parse the options
+    contextlines = 2
+    verbose = 1
+    subject_prefix = ""
+    replyto = None
+    fromhost = None
     for opt, arg in opts:
         if opt in ('-h', '--help'):
             usage(0)
@@ -174,6 +328,17 @@
                 contextlines = 2
         elif opt == '-u':
             contextlines = 0
+        elif opt in ('-S', '--subject-prefix'):
+            subject_prefix = arg
+        elif opt in ('-R', '--reply-to'):
+            replyto = arg
+        elif opt in ('-q', '--quiet'):
+            verbose = 0
+        elif opt in ('-f', '--fromhost'):
+            fromhost = arg
+        elif opt in ('-m', '--mailhost'):
+            global MAILHOST
+            MAILHOST = arg
 
     # What follows is the specification containing the files that were
     # modified.  The argument actually must be split, with the first component
@@ -181,7 +346,7 @@
     # $CVSROOT, followed by the list of files that are changing.
     if not args:
         usage(1, 'No CVS module specified')
-    SUBJECT = args[0]
+    subject = subject_prefix + args[0]
     specs = string.split(args[0])
     del args[0]
 
@@ -190,26 +355,30 @@
         usage(1, 'No recipients specified')
 
     # Now do the mail command
-    PEOPLE = string.join(args)
-    mailcmd = MAILCMD % vars()
+    people = args
 
-    print 'Mailing %s...' % PEOPLE
-    if specs == ['-', 'Imported', 'sources']:
-        return
-    if specs[-3:] == ['-', 'New', 'directory']:
+    if verbose:
+        print 'Mailing %s...' % string.join(people, COMMASPACE)
+
+    if specs[-3:] == ['-', 'Imported', 'sources']:
+        del specs[-3:]
+    elif specs[-3:] == ['-', 'New', 'directory']:
         del specs[-3:]
     elif len(specs) > 2:
         L = specs[:2]
         for s in specs[2:]:
             prev = L[-1]
-            if string.count(prev, ",") < 2:
+            if string.count(prev, ',') < 2:
                 L[-1] = "%s %s" % (prev, s)
             else:
                 L.append(s)
         specs = L
-    print 'Generating notification message...'
-    blast_mail(mailcmd, specs[1:], contextlines)
-    print 'Generating notification message... done.'
+
+    if verbose:
+        print 'Generating notification message...'
+    blast_mail(subject, people, specs[1:], contextlines, fromhost, replyto)
+    if verbose:
+        print 'Generating notification message... done.'
 
 
 





More information about the cairo-commit mailing list