1. Ronald Oussoren
  2. pyobjc

Commits

Just van Rossum  committed e79744b

syncmail Python script, to generate checkins emails

  • Participants
  • Parent commits 1fffc7a
  • Branches default

Comments (0)

Files changed (1)

File CVSROOT/syncmail

View file
  • Ignore whitespace
+#! /usr/bin/python
+#  -*- Python -*-
+
+"""Complicated notification for CVS checkins.
+
+This script is used to provide email notifications of changes to the CVS
+repository.  These email changes will include context diffs of the changes.
+Really big diffs will be trimmed.
+
+This script is run from a CVS loginfo file (see $CVSROOT/CVSROOT/loginfo).  To
+set this up, create a loginfo entry that looks something like this:
+
+    mymodule /path/to/this/script %%s some-email-addr@your.domain
+
+In this example, whenever a checkin that matches `mymodule' is made, this
+script is invoked, which will generate the diff containing email, and send it
+to some-email-addr@your.domain.
+
+    Note: This module used to also do repository synchronizations via
+    rsync-over-ssh, but since the repository has been moved to SourceForge,
+    this is no longer necessary.  The syncing functionality has been ripped
+    out in the 3.0, which simplifies it considerably.  Access the 2.x versions
+    to refer to this functionality.  Because of this, the script is misnamed.
+
+It no longer makes sense to run this script from the command line.  Doing so
+will only print out this usage information.
+
+Usage:
+
+    %(PROGRAM)s [options] <%%S> email-addr [email-addr ...]
+
+Where options is:
+
+    --cvsroot=<path>
+    	Use <path> as the environment variable CVSROOT.  Otherwise this
+    	variable must exist in the environment.
+
+    --help
+    -h
+        Print this text.
+
+    --context=#
+    -C #
+        Include # lines of context around lines that differ (default: 2).
+
+    -c
+        Produce a context diff (default).
+
+    -u
+        Produce a unified diff (smaller, but harder to read).
+
+    <%%S>
+        CVS %%s loginfo expansion.  When invoked by CVS, this will be a single
+        string containing the directory the checkin is being made in, relative
+        to $CVSROOT, followed by the list of files that are changing.  If the
+        %%s in the loginfo file is %%{sVv}, context diffs for each of the
+        modified files are included in any email messages that are generated.
+
+    email-addrs
+        At least one email address.
+
+"""
+
+import os
+import sys
+import string
+import time
+import getopt
+
+# Notification command
+MAILCMD = '/bin/mail -s "CVS: %(SUBJECT)s" %(PEOPLE)s 2>&1 > /dev/null'
+
+# Diff trimming stuff
+DIFF_HEAD_LINES = 20
+DIFF_TAIL_LINES = 20
+DIFF_TRUNCATE_IF_LARGER = 1000
+
+PROGRAM = sys.argv[0]
+
+
+ 
+def usage(code, msg=''):
+    print __doc__ % globals()
+    if msg:
+        print msg
+    sys.exit(code)
+
+
+ 
+def calculate_diff(filespec, contextlines):
+    try:
+        file, oldrev, newrev = string.split(filespec, ',')
+    except ValueError:
+        # No diff to report
+        return '***** Bogus filespec: %s' % filespec
+    if oldrev == 'NONE':
+        try:
+            if os.path.exists(file):
+                fp = open(file)
+            else:
+                update_cmd = 'cvs -fn update -r %s -p %s' % (newrev, file)
+                fp = os.popen(update_cmd)
+            lines = fp.readlines()
+            fp.close()
+            lines.insert(0, '--- NEW FILE: %s ---\n' % file)
+        except IOError, e:
+            lines = ['***** Error reading new file: ',
+                     str(e), '\n***** file: ', file, ' cwd: ', os.getcwd()]
+    elif newrev == 'NONE':
+        lines = ['--- %s DELETED ---\n' % file]
+    else:
+        # 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)
+        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)
+    return string.join(lines, '')
+
+
+ 
+def blast_mail(mailcmd, filestodiff, contextlines):
+    # 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
+        os._exit(0)
+
+
+ 
+# scan args for options
+def main():
+    contextlines = 2
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], 'hC:cu',
+                                   ['context=', 'cvsroot=', 'help'])
+    except getopt.error, msg:
+        usage(1, msg)
+
+    # parse the options
+    for opt, arg in opts:
+        if opt in ('-h', '--help'):
+            usage(0)
+        elif opt == '--cvsroot':
+            os.environ['CVSROOT'] = arg
+        elif opt in ('-C', '--context'):
+            contextlines = int(arg)
+        elif opt == '-c':
+            if contextlines <= 0:
+                contextlines = 2
+        elif opt == '-u':
+            contextlines = 0
+
+    # What follows is the specification containing the files that were
+    # modified.  The argument actually must be split, with the first component
+    # containing the directory the checkin is being made in, relative to
+    # $CVSROOT, followed by the list of files that are changing.
+    if not args:
+        usage(1, 'No CVS module specified')
+    SUBJECT = args[0]
+    specs = string.split(args[0])
+    del args[0]
+
+    # The remaining args should be the email addresses
+    if not args:
+        usage(1, 'No recipients specified')
+
+    # Now do the mail command
+    PEOPLE = string.join(args)
+    mailcmd = MAILCMD % vars()
+
+    print 'Mailing %s...' % PEOPLE
+    if specs == ['-', 'Imported', 'sources']:
+        return
+    if 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:
+                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 __name__ == '__main__':
+    main()
+    sys.exit(0)