--- /dev/null
+#! /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 -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:]
+ print 'Generating notification message...'
+ blast_mail(mailcmd, specs[1:], contextlines)
+ print 'Generating notification message... done.'
+
+
+
+if __name__ == '__main__':
+ main()
+ sys.exit(0)
\ No newline at end of file