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.
30 %(PROGRAM)s [options] <%%S> email-addr [email-addr ...]
35 Use <path> as the environment variable CVSROOT. Otherwise this
36 variable must exist in the environment.
44 Include # lines of context around lines that differ (default: 2).
47 Produce a context diff (default).
50 Produce a unified diff (smaller, but harder to read).
53 Mark diff as made by user.
56 CVS %%s loginfo expansion. When invoked by CVS, this will be a single
57 string containing the directory the checkin is being made in, relative
58 to $CVSROOT, followed by the list of files that are changing. If the
59 %%s in the loginfo file is %%{sVv}, context diffs for each of the
60 modified files are included in any email messages that are generated.
63 At least one email address.
73 # Notification command
74 MAILCMD
= '/bin/mail -s "%(username)s: %(SUBJECT)s" %(PEOPLE)s 2>&1 > /dev/null'
79 DIFF_TRUNCATE_IF_LARGER
= 1000
85 def usage(code
, msg
=''):
86 print __doc__
% globals()
93 def calculate_diff(filespec
, contextlines
):
95 file, oldrev
, newrev
= string
.split(filespec
, ',')
98 return '***** Bogus filespec: %s' % filespec
101 if os
.path
.exists(file):
104 update_cmd
= 'cvs -fn update -r %s -p %s' % (newrev
, file)
105 fp
= os
.popen(update_cmd
)
106 lines
= fp
.readlines()
108 lines
.insert(0, '--- NEW FILE: %s ---\n' % file)
110 lines
= ['***** Error reading new file: ',
111 str(e
), '\n***** file: ', file, ' cwd: ', os
.getcwd()]
112 elif newrev
== 'NONE':
113 lines
= ['--- %s DELETED ---\n' % file]
115 # This /has/ to happen in the background, otherwise we'll run into CVS
116 # lock contention. What a crock.
118 difftype
= "-C " + str(contextlines
)
121 diffcmd
= '/usr/bin/cvs -f diff -kk %s -b -r %s -r %s %s' % (
122 difftype
, oldrev
, newrev
, file)
123 fp
= os
.popen(diffcmd
)
124 lines
= fp
.readlines()
126 # ignore the error code, it always seems to be 1 :(
128 ## return 'Error code %d occurred during diff\n' % (sts >> 8)
129 if len(lines
) > DIFF_TRUNCATE_IF_LARGER
:
130 removedlines
= len(lines
) - DIFF_HEAD_LINES
- DIFF_TAIL_LINES
131 del lines
[DIFF_HEAD_LINES
:-DIFF_TAIL_LINES
]
132 lines
.insert(DIFF_HEAD_LINES
,
133 '[...%d lines suppressed...]\n' % removedlines
)
134 return string
.join(lines
, '')
138 def blast_mail(mailcmd
, filestodiff
, contextlines
):
139 # cannot wait for child process or that will cause parent to retain cvs
140 # lock for too long. Urg!
143 # give up the lock you cvs thang!
145 fp
= os
.popen(mailcmd
, 'w')
146 fp
.write(sys
.stdin
.read())
148 # append the diffs if available
149 for file in filestodiff
:
150 fp
.write(calculate_diff(file, contextlines
))
153 # doesn't matter what code we return, it isn't waited on
158 # scan args for options
162 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'hC:cuU:',
163 ['context=', 'cvsroot=', 'help'])
164 except getopt
.error
, msg
:
170 for opt
, arg
in opts
:
171 if opt
in ('-h', '--help'):
173 elif opt
== '--cvsroot':
174 os
.environ
['CVSROOT'] = arg
175 elif opt
in ('-C', '--context'):
176 contextlines
= int(arg
)
178 if contextlines
<= 0:
185 # What follows is the specification containing the files that were
186 # modified. The argument actually must be split, with the first component
187 # containing the directory the checkin is being made in, relative to
188 # $CVSROOT, followed by the list of files that are changing.
190 usage(1, 'No CVS module specified')
192 specs
= string
.split(args
[0])
195 # The remaining args should be the email addresses
197 usage(1, 'No recipients specified')
199 # Now do the mail command
200 PEOPLE
= string
.join(args
)
201 mailcmd
= MAILCMD
% vars()
203 print 'Mailing %s...' % PEOPLE
204 if specs
== ['-', 'Imported', 'sources']:
206 if specs
[-3:] == ['-', 'New', 'directory']:
208 print 'Generating notification message...'
209 blast_mail(mailcmd
, specs
[1:], contextlines
)
210 print 'Generating notification message... done.'
214 if __name__
== '__main__':