210 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			210 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#! /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)
 |