| #! /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 = "-uN" |
| diffcmd = '/usr/bin/cvs -f diff -kk %s -b -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) |
| |