Develop and Download Open Source Software

Browse CVS Repository

Contents of /freetrain/CVSROOT/syncmail

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download)
Thu Jan 16 04:15:35 2003 UTC (21 years, 2 months ago) by kkawa
Branch: MAIN
added better cvs notification tool

1 #! /usr/bin/python
2
3 # NOTE: Until SourceForge installs a modern version of Python on the cvs
4 # servers, this script MUST be compatible with Python 1.5.2.
5
6 """Complicated notification for CVS checkins.
7
8 This script is used to provide email notifications of changes to the CVS
9 repository. These email changes will include context diffs of the changes.
10 Really big diffs will be trimmed.
11
12 This script is run from a CVS loginfo file (see $CVSROOT/CVSROOT/loginfo). To
13 set this up, create a loginfo entry that looks something like this:
14
15 mymodule /path/to/this/script %%s some-email-addr@your.domain
16
17 In this example, whenever a checkin that matches `mymodule' is made, this
18 script is invoked, which will generate the diff containing email, and send it
19 to some-email-addr@your.domain.
20
21 Note: This module used to also do repository synchronizations via
22 rsync-over-ssh, but since the repository has been moved to SourceForge,
23 this is no longer necessary. The syncing functionality has been ripped
24 out in the 3.0, which simplifies it considerably. Access the 2.x versions
25 to refer to this functionality. Because of this, the script is misnamed.
26
27 It no longer makes sense to run this script from the command line. Doing so
28 will only print out this usage information.
29
30 Usage:
31
32 %(PROGRAM)s [options] <%%S> email-addr [email-addr ...]
33
34 Where options are:
35
36 --cvsroot=<path>
37 Use <path> as the environment variable CVSROOT. Otherwise this
38 variable must exist in the environment.
39
40 --context=#
41 -C #
42 Include # lines of context around lines that differ (default: 2).
43
44 -c
45 Produce a context diff (default).
46
47 -u
48 Produce a unified diff (smaller).
49
50 --quiet / -q
51 Don't print as much status to stdout.
52
53 --fromhost=hostname
54 -f hostname
55 The hostname that email messages appear to be coming from. The From:
56 header will of the outgoing message will look like user@hostname. By
57 default, hostname is the machine's fully qualified domain name.
58
59 --help / -h
60 Print this text.
61
62 The rest of the command line arguments are:
63
64 <%%S>
65 CVS %%s loginfo expansion. When invoked by CVS, this will be a single
66 string containing the directory the checkin is being made in, relative
67 to $CVSROOT, followed by the list of files that are changing. If the
68 %%s in the loginfo file is %%{sVv}, context diffs for each of the
69 modified files are included in any email messages that are generated.
70
71 email-addrs
72 At least one email address.
73 """
74 import os
75 import sys
76 import re
77 import time
78 import string
79 import getopt
80 import smtplib
81 import pwd
82 import socket
83
84 try:
85 from socket import getfqdn
86 except ImportError:
87 def getfqdn():
88 # Python 1.5.2 :(
89 hostname = socket.gethostname()
90 byaddr = socket.gethostbyaddr(socket.gethostbyname(hostname))
91 aliases = byaddr[1]
92 aliases.insert(0, byaddr[0])
93 aliases.insert(0, hostname)
94 for fqdn in aliases:
95 if '.' in fqdn:
96 break
97 else:
98 fqdn = 'localhost.localdomain'
99 return fqdn
100
101
102 from cStringIO import StringIO
103
104 # Which SMTP server to do we connect to? Empty string means localhost.
105 MAILHOST = ''
106 MAILPORT = 25
107
108 # Diff trimming stuff
109 DIFF_HEAD_LINES = 20
110 DIFF_TAIL_LINES = 20
111 DIFF_TRUNCATE_IF_LARGER = 1000
112
113 EMPTYSTRING = ''
114 SPACE = ' '
115 DOT = '.'
116 COMMASPACE = ', '
117
118 PROGRAM = sys.argv[0]
119
120 BINARY_EXPLANATION_LINES = [
121 "(This appears to be a binary file; contents omitted.)\n"
122 ]
123
124 REVCRE = re.compile("^(NONE|[0-9.]+)$")
125 NOVERSION = "Couldn't generate diff; no version number found in filespec: %s"
126 BACKSLASH = "Couldn't generate diff: backslash in filespec's filename: %s"
127
128
129
130 def usage(code, msg=''):
131 print __doc__ % globals()
132 if msg:
133 print msg
134 sys.exit(code)
135
136
137
138 def calculate_diff(filespec, contextlines):
139 file, oldrev, newrev = string.split(filespec, ',')
140 # Make sure we can find a CVS version number
141 if not REVCRE.match(oldrev):
142 return NOVERSION % filespec
143 if not REVCRE.match(newrev):
144 return NOVERSION % filespec
145
146 if string.find(file, '\\') <> -1:
147 # I'm sorry, a file name that contains a backslash is just too much.
148 # XXX if someone wants to figure out how to escape the backslashes in
149 # a safe way to allow filenames containing backslashes, this is the
150 # place to do it. --Zooko 2002-03-17
151 return BACKSLASH % filespec
152
153 if string.find(file, "'") <> -1:
154 # Those crazy users put single-quotes in their file names! Now we
155 # have to escape everything that is meaningful inside double-quotes.
156 filestr = string.replace(file, '`', '\`')
157 filestr = string.replace(filestr, '"', '\"')
158 filestr = string.replace(filestr, '$', '\$')
159 # and quote it with double-quotes.
160 filestr = '"' + filestr + '"'
161 else:
162 # quote it with single-quotes.
163 filestr = "'" + file + "'"
164 if oldrev == 'NONE':
165 try:
166 if os.path.exists(file):
167 fp = open(file)
168 else:
169 update_cmd = "cvs -fn update -r %s -p %s" % (newrev, filestr)
170 fp = os.popen(update_cmd)
171 lines = fp.readlines()
172 fp.close()
173 # Is this a binary file? Let's look at the first few
174 # lines to figure it out:
175 for line in lines[:5]:
176 for c in string.rstrip(line):
177 if c in string.whitespace:
178 continue
179 if c < ' ' or c > chr(127):
180 lines = BINARY_EXPLANATION_LINES[:]
181 break
182 lines.insert(0, '--- NEW FILE: %s ---\n' % file)
183 except IOError, e:
184 lines = ['***** Error reading new file: ',
185 str(e), '\n***** file: ', file, ' cwd: ', os.getcwd()]
186 elif newrev == 'NONE':
187 lines = ['--- %s DELETED ---\n' % file]
188 else:
189 # This /has/ to happen in the background, otherwise we'll run into CVS
190 # lock contention. What a crock.
191 if contextlines > 0:
192 difftype = "-C " + str(contextlines)
193 else:
194 difftype = "-u"
195 diffcmd = "/usr/bin/cvs -f diff -kk %s --minimal -r %s -r %s %s" \
196 % (difftype, oldrev, newrev, filestr)
197 fp = os.popen(diffcmd)
198 lines = fp.readlines()
199 sts = fp.close()
200 # ignore the error code, it always seems to be 1 :(
201 ## if sts:
202 ## return 'Error code %d occurred during diff\n' % (sts >> 8)
203 if len(lines) > DIFF_TRUNCATE_IF_LARGER:
204 removedlines = len(lines) - DIFF_HEAD_LINES - DIFF_TAIL_LINES
205 del lines[DIFF_HEAD_LINES:-DIFF_TAIL_LINES]
206 lines.insert(DIFF_HEAD_LINES,
207 '[...%d lines suppressed...]\n' % removedlines)
208 return string.join(lines, '')
209
210
211
212 def blast_mail(subject, people, filestodiff, contextlines, fromhost):
213 # cannot wait for child process or that will cause parent to retain cvs
214 # lock for too long. Urg!
215 if not os.fork():
216 # in the child
217 # give up the lock you cvs thang!
218 time.sleep(2)
219 # Create the smtp connection to the localhost
220 conn = smtplib.SMTP()
221 conn.connect(MAILHOST, MAILPORT)
222 user = pwd.getpwuid(os.getuid())[0]
223 domain = fromhost or getfqdn()
224 author = '%s@%s' % (user, domain)
225 s = StringIO()
226 sys.stdout = s
227 try:
228 print '''\
229 From: %(author)s
230 To: %(people)s
231 Subject: %(subject)s
232 ''' % {'author' : author,
233 'people' : string.join(people, COMMASPACE),
234 'subject': subject,
235 }
236 s.write(sys.stdin.read())
237 # append the diffs if available
238 print
239 for file in filestodiff:
240 print calculate_diff(file, contextlines)
241 finally:
242 sys.stdout = sys.__stdout__
243 resp = conn.sendmail(author, people, s.getvalue())
244 conn.close()
245 os._exit(0)
246
247
248
249 # scan args for options
250 def main():
251 try:
252 opts, args = getopt.getopt(
253 sys.argv[1:], 'hC:cuqf:',
254 ['fromhost=', 'context=', 'cvsroot=', 'help', 'quiet'])
255 except getopt.error, msg:
256 usage(1, msg)
257
258 # parse the options
259 contextlines = 2
260 verbose = 1
261 fromhost = None
262 for opt, arg in opts:
263 if opt in ('-h', '--help'):
264 usage(0)
265 elif opt == '--cvsroot':
266 os.environ['CVSROOT'] = arg
267 elif opt in ('-C', '--context'):
268 contextlines = int(arg)
269 elif opt == '-c':
270 if contextlines <= 0:
271 contextlines = 2
272 elif opt == '-u':
273 contextlines = 0
274 elif opt in ('-q', '--quiet'):
275 verbose = 0
276 elif opt in ('-f', '--fromhost'):
277 fromhost = arg
278
279 # What follows is the specification containing the files that were
280 # modified. The argument actually must be split, with the first component
281 # containing the directory the checkin is being made in, relative to
282 # $CVSROOT, followed by the list of files that are changing.
283 if not args:
284 usage(1, 'No CVS module specified')
285 subject = args[0]
286 specs = string.split(args[0])
287 del args[0]
288
289 # The remaining args should be the email addresses
290 if not args:
291 usage(1, 'No recipients specified')
292
293 # Now do the mail command
294 people = args
295
296 if verbose:
297 print 'Mailing %s...' % string.join(people, COMMASPACE)
298
299 if specs == ['-', 'Imported', 'sources']:
300 return
301 if specs[-3:] == ['-', 'New', 'directory']:
302 del specs[-3:]
303 elif len(specs) > 2:
304 L = specs[:2]
305 for s in specs[2:]:
306 prev = L[-1]
307 if string.count(prev, ',') < 2:
308 L[-1] = "%s %s" % (prev, s)
309 else:
310 L.append(s)
311 specs = L
312
313 if verbose:
314 print 'Generating notification message...'
315 blast_mail(subject, people, specs[1:], contextlines, fromhost)
316 if verbose:
317 print 'Generating notification message... done.'
318
319
320
321 if __name__ == '__main__':
322 main()
323 sys.exit(0)

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26