4 """Complicated notification for CVS checkins.
6 This script is used to provide email notifications of changes to the CVS
7 repository. These email changes will include context diffs of the changes.
8 Really big diffs will be trimmed.
10 This script is run from a CVS loginfo file (see $CVSROOT/CVSROOT/loginfo). To
11 set this up, create a loginfo entry that looks something like this:
13 mymodule /path/to/this/script %%s some-email-addr@your.domain
15 In this example, whenever a checkin that matches `mymodule' is made, this
16 script is invoked, which will generate the diff containing email, and send it
17 to some-email-addr@your.domain.
19 Note: This module used to also do repository synchronizations via
20 rsync-over-ssh, but since the repository has been moved to SourceForge,
21 this is no longer necessary. The syncing functionality has been ripped
22 out in the 3.0, which simplifies it considerably. Access the 2.x versions
23 to refer to this functionality. Because of this, the script is misnamed.
25 It no longer makes sense to run this script from the command line. Doing so
26 will only print out this usage information.
31 %(PROGRAM)s [options] <%%S> email-addr [email-addr ...]
36 Use <path> as the environment variable CVSROOT. Otherwise this
37 variable must exist in the environment.
44 CVS %%s loginfo expansion. When invoked by CVS, this will be a single
45 string containing the directory the checkin is being made in, relative
46 to $CVSROOT, followed by the list of files that are changing. If the
47 %%s in the loginfo file is %%{sVv}, context diffs for each of the
48 modified files are included in any email messages that are generated.
51 At least one email address.
61 # Notification command
62 MAILCMD = '/bin/mail -s "CVS: %(SUBJECT)s" %(PEOPLE)s 2>&1 > /dev/null'
67 DIFF_TRUNCATE_IF_LARGER = 1000
73 def usage(code, msg=''):
74 print __doc__ % globals()
81 def calculate_diff(filespec):
83 file, oldrev, newrev = string.split(filespec, ',')
86 return '***** Bogus filespec: %s' % filespec
89 if os.path.exists(file):
92 update_cmd = 'cvs -fn update -r %s -p %s' % (newrev, file)
93 fp = os.popen(update_cmd)
94 lines = fp.readlines()
96 lines.insert(0, '--- NEW FILE ---\n')
98 lines = ['***** Error reading new file: ',
99 str(e), '\n***** file: ', file, ' cwd: ', os.getcwd()]
100 elif newrev == 'NONE':
101 lines = ['--- %s DELETED ---\n' % file]
103 # This /has/ to happen in the background, otherwise we'll run into CVS
104 # lock contention. What a crock.
105 diffcmd = '/usr/bin/cvs -f diff -kk -C 2 -w -r %s -r %s %s' % (
106 oldrev, newrev, file)
107 fp = os.popen(diffcmd)
108 lines = fp.readlines()
110 # ignore the error code, it always seems to be 1 :(
112 ## return 'Error code %d occurred during diff\n' % (sts >> 8)
113 if len(lines) > DIFF_TRUNCATE_IF_LARGER:
114 removedlines = len(lines) - DIFF_HEAD_LINES - DIFF_TAIL_LINES
115 del lines[DIFF_HEAD_LINES:-DIFF_TAIL_LINES]
116 lines.insert(DIFF_HEAD_LINES,
117 '[...%d lines suppressed...]\n' % removedlines)
118 return string.join(lines, '')
122 def blast_mail(mailcmd, filestodiff):
123 # cannot wait for child process or that will cause parent to retain cvs
124 # lock for too long. Urg!
127 # give up the lock you cvs thang!
129 fp = os.popen(mailcmd, 'w')
130 fp.write(sys.stdin.read())
132 # append the diffs if available
133 for file in filestodiff:
134 fp.write(calculate_diff(file))
137 # doesn't matter what code we return, it isn't waited on
142 # scan args for options
145 opts, args = getopt.getopt(sys.argv[1:], 'h', ['cvsroot=', 'help'])
146 except getopt.error, msg:
150 for opt, arg in opts:
151 if opt in ('-h', '--help'):
153 elif opt == '--cvsroot':
154 os.environ['CVSROOT'] = arg
156 # What follows is the specification containing the files that were
157 # modified. The argument actually must be split, with the first component
158 # containing the directory the checkin is being made in, relative to
159 # $CVSROOT, followed by the list of files that are changing.
161 usage(1, 'No CVS module specified')
163 specs = string.split(args[0])
166 # The remaining args should be the email addresses
168 usage(1, 'No recipients specified')
170 # Now do the mail command
171 PEOPLE = string.join(args)
172 mailcmd = MAILCMD % vars()
174 print 'Mailing %s...' % PEOPLE
175 if specs == ['-', 'Imported', 'sources']:
177 if specs[-3:] == ['-', 'New', 'directory']:
179 print 'Generating notification message...'
180 blast_mail(mailcmd, specs[1:])
181 print 'Generating notification message... done.'
185 if __name__ == '__main__':