Develop and Download Open Source Software

Browse CVS Repository

Annotation of /freetrain/CVSROOT/syncmail

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


Revision 1.2 - (hide annotations) (download)
Thu Jan 16 04:24:58 2003 UTC (21 years, 2 months ago) by kkawa
Branch: MAIN
Changes since 1.1: +5 -5 lines
testing

1 kkawa 1.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 kkawa 1.2 MAILHOST = 'www.kohsuke.org'
106 kkawa 1.1 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 kkawa 1.2
138 kkawa 1.1 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 kkawa 1.2
212 kkawa 1.1 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 kkawa 1.2
249 kkawa 1.1 # 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 kkawa 1.2
321 kkawa 1.1 if __name__ == '__main__':
322     main()
323     sys.exit(0)

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