blob: fa24a8052a3aae0e725c6f9b7b0347e833c12e9e [file] [log] [blame]
Björn Stenberg0375f312002-04-11 08:49:16 +00001#! /usr/bin/python
2# -*- Python -*-
3
4"""Complicated notification for CVS checkins.
5
6This script is used to provide email notifications of changes to the CVS
7repository. These email changes will include context diffs of the changes.
8Really big diffs will be trimmed.
9
10This script is run from a CVS loginfo file (see $CVSROOT/CVSROOT/loginfo). To
11set this up, create a loginfo entry that looks something like this:
12
13 mymodule /path/to/this/script %%s some-email-addr@your.domain
14
15In this example, whenever a checkin that matches `mymodule' is made, this
16script is invoked, which will generate the diff containing email, and send it
17to some-email-addr@your.domain.
18
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.
24
25It no longer makes sense to run this script from the command line. Doing so
26will only print out this usage information.
27
28Usage:
29
30 %(PROGRAM)s [options] <%%S> email-addr [email-addr ...]
31
32Where options is:
33
34 --cvsroot=<path>
35Use <path> as the environment variable CVSROOT. Otherwise this
36 variable must exist in the environment.
37
38 --help
39 -h
40 Print this text.
41
42 --context=#
43 -C #
44 Include # lines of context around lines that differ (default: 2).
45
46 -c
47 Produce a context diff (default).
48
49 -u
50 Produce a unified diff (smaller, but harder to read).
51
Björn Stenberg59fb25d2004-05-07 12:22:06 +000052 -U user
53 Mark diff as made by user.
54
Björn Stenberg0375f312002-04-11 08:49:16 +000055 <%%S>
56CVS %%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.
61
62 email-addrs
63 At least one email address.
64
65"""
66
67import os
68import sys
69import string
70import time
71import getopt
72
73# Notification command
Björn Stenberg39a84d02004-05-07 12:37:43 +000074MAILCMD = '/bin/mail -s "%(username)s: %(SUBJECT)s" %(PEOPLE)s 2>&1 > /dev/null'
Björn Stenberg0375f312002-04-11 08:49:16 +000075
76# Diff trimming stuff
77DIFF_HEAD_LINES = 20
78DIFF_TAIL_LINES = 20
79DIFF_TRUNCATE_IF_LARGER = 1000
80
81PROGRAM = sys.argv[0]
82
83
84
85def usage(code, msg=''):
86 print __doc__ % globals()
87 if msg:
88 print msg
89 sys.exit(code)
90
91
92
93def calculate_diff(filespec, contextlines):
94 try:
95 file, oldrev, newrev = string.split(filespec, ',')
96 except ValueError:
97 # No diff to report
98 return '***** Bogus filespec: %s' % filespec
99 if oldrev == 'NONE':
100 try:
101 if os.path.exists(file):
102 fp = open(file)
103 else:
104 update_cmd = 'cvs -fn update -r %s -p %s' % (newrev, file)
105 fp = os.popen(update_cmd)
106 lines = fp.readlines()
107 fp.close()
108 lines.insert(0, '--- NEW FILE: %s ---\n' % file)
109 except IOError, e:
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]
114 else:
115 # This /has/ to happen in the background, otherwise we'll run into CVS
116 # lock contention. What a crock.
117 if contextlines > 0:
118 difftype = "-C " + str(contextlines)
119 else:
120 difftype = "-uN"
Björn Stenberg6ec42d92002-05-23 12:06:31 +0000121 diffcmd = '/usr/bin/cvs -f diff -kk %s -b -r %s -r %s %s' % (
Björn Stenberg0375f312002-04-11 08:49:16 +0000122 difftype, oldrev, newrev, file)
123 fp = os.popen(diffcmd)
124 lines = fp.readlines()
125 sts = fp.close()
126 # ignore the error code, it always seems to be 1 :(
127## if sts:
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, '')
135
136
137
138def 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!
141 if not os.fork():
142 # in the child
143 # give up the lock you cvs thang!
144 time.sleep(2)
145 fp = os.popen(mailcmd, 'w')
146 fp.write(sys.stdin.read())
147 fp.write('\n')
148 # append the diffs if available
149 for file in filestodiff:
150 fp.write(calculate_diff(file, contextlines))
151 fp.write('\n')
152 fp.close()
153 # doesn't matter what code we return, it isn't waited on
154 os._exit(0)
155
156
157
158# scan args for options
159def main():
160 contextlines = 2
161 try:
Björn Stenberg6ed32802004-05-07 12:32:23 +0000162 opts, args = getopt.getopt(sys.argv[1:], 'hC:cuU:',
Björn Stenberg0375f312002-04-11 08:49:16 +0000163 ['context=', 'cvsroot=', 'help'])
164 except getopt.error, msg:
165 usage(1, msg)
166
Björn Stenberg1e0cb1f2004-05-07 12:37:08 +0000167 username = 'unknown'
Björn Stenberg512d51c2004-05-07 12:26:14 +0000168
Björn Stenberg0375f312002-04-11 08:49:16 +0000169 # parse the options
170 for opt, arg in opts:
171 if opt in ('-h', '--help'):
172 usage(0)
173 elif opt == '--cvsroot':
174 os.environ['CVSROOT'] = arg
175 elif opt in ('-C', '--context'):
176 contextlines = int(arg)
177 elif opt == '-c':
178 if contextlines <= 0:
179 contextlines = 2
180 elif opt == '-u':
181 contextlines = 0
Björn Stenberg59fb25d2004-05-07 12:22:06 +0000182 elif opt == '-U':
183 username = arg
Björn Stenberg0375f312002-04-11 08:49:16 +0000184
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.
189 if not args:
190 usage(1, 'No CVS module specified')
191 SUBJECT = args[0]
192 specs = string.split(args[0])
193 del args[0]
194
195 # The remaining args should be the email addresses
196 if not args:
197 usage(1, 'No recipients specified')
198
199 # Now do the mail command
200 PEOPLE = string.join(args)
201 mailcmd = MAILCMD % vars()
202
203 print 'Mailing %s...' % PEOPLE
204 if specs == ['-', 'Imported', 'sources']:
205 return
206 if specs[-3:] == ['-', 'New', 'directory']:
207 del specs[-3:]
Björn Stenberg1e0cb1f2004-05-07 12:37:08 +0000208 print 'Generating notification message...'
209 blast_mail(mailcmd, specs[1:], contextlines)
Björn Stenberg0375f312002-04-11 08:49:16 +0000210 print 'Generating notification message... done.'
211
212
Björn Stenberg0375f312002-04-11 08:49:16 +0000213
214if __name__ == '__main__':
215 main()
216 sys.exit(0)
217