Changelog update.
[debian_buildbot.git] / master / contrib / buildbot_cvs_mail.py
blob3b25470c08e09bffd2206c86a3cc09e7bb9600ea
1 #!/usr/bin/env python
3 # Buildbot CVS Mail
5 # This script was derrived from syncmail,
6 # Copyright (c) 2002-2006 Barry Warsaw, Fred Drake, and contributors
8 # http://cvs-syncmail.cvs.sourceforge.net
10 # The script was re-written with the sole pupose of providing updates to
11 # Buildbot master by Andy Howell
13 # Options handling done right by djmitche
16 """
18 --testing
19 Construct message and send to stdout for testing
21 The rest of the command line arguments are:
23 %%{sVv}
24 CVS %%{sVv} loginfo expansion. When invoked by CVS, this will be a single
25 string containing the files that are changing.
27 """
28 __version__ = '$Revision: 1.3 $'
30 import os
31 import re
32 import sys
33 import time
34 import getopt
35 import socket
36 import smtplib
37 import textwrap
38 import optparse
40 from cStringIO import StringIO
41 from email.Utils import formataddr
43 try:
44 import pwd
45 except:
46 # pwd is not available on Windows..
47 pwd = None
49 COMMASPACE = ', '
51 PROGRAM = sys.argv[0]
53 class SmtplibMock:
54 """I stand in for smtplib for testing purposes.
55 """
56 class SMTP:
57 """I stand in for smtplib.SMTP connection for testing purposes.
58 I copy the message to stdout.
59 """
60 def close(self):
61 pass
62 def connect(self, mailhost, mailport):
63 pass
64 def sendmail(self, address, email, msg):
65 sys.stdout.write( msg )
68 rfc822_specials_re = re.compile(r'[\(\)\<\>\@\,\;\:\\\"\.\[\]]')
70 def quotename(name):
71 if name and rfc822_specials_re.search(name):
72 return '"%s"' % name.replace('"', '\\"')
73 else:
74 return name
76 def send_mail(options):
77 # Create the smtp connection to the localhost
78 conn = options.smtplib.SMTP()
79 conn.connect(options.mailhost, options.mailport)
80 if pwd:
81 pwinfo = pwd.getpwuid(os.getuid())
82 user = pwinfo[0]
83 name = pwinfo[4]
84 else:
85 user = 'cvs'
86 name = 'CVS'
88 domain = options.fromhost
89 if not domain:
90 # getfqdn is not good for use in unit tests
91 if options.amTesting:
92 domain = 'testing.com'
93 else:
94 domain = socket.getfqdn()
95 address = '%s@%s' % (user, domain)
96 s = StringIO()
97 datestamp = time.strftime('%a, %d %b %Y %H:%M:%S +0000',
98 time.gmtime(time.time()))
99 fileList = ' '.join(map(str, options.files))
101 vars = {'author' : formataddr((name, address)),
102 'email' : options.email,
103 'subject' : 'cvs update for project %s' % options.project,
104 'version' : __version__,
105 'date' : datestamp,
107 print >> s, '''\
108 From: %(author)s
109 To: %(email)s''' % vars
110 if options.replyto:
111 print >> s, 'Reply-To: %s' % options.replyto
112 print >>s, '''\
113 Subject: %(subject)s
114 Date: %(date)s
115 X-Mailer: Python buildbot-cvs-mail %(version)s
116 ''' % vars
117 print >> s, 'Cvsmode: %s' % options.cvsmode
118 print >> s, 'Category: %s' % options.category
119 print >> s, 'CVSROOT: %s' % options.cvsroot
120 print >> s, 'Files: %s' % fileList
121 if options.path:
122 print >> s, 'Path: %s' % options.path
123 print >> s, 'Project: %s' % options.project
124 s.write(sys.stdin.read())
125 print >> s
126 resp = conn.sendmail(address, options.email, s.getvalue())
127 conn.close()
129 def fork_and_send_mail(options):
130 # cannot wait for child process or that will cause parent to retain cvs
131 # lock for too long. Urg!
132 if not os.fork():
133 # in the child
134 # give up the lock you cvs thang!
135 time.sleep(2)
136 send_mail(options)
137 os._exit(0)
139 description="""
140 This script is used to provide email notifications of changes to the CVS
141 repository to a buildbot master. It is invoked via a CVS loginfo file (see
142 $CVSROOT/CVSROOT/loginfo). See the Buildbot manual for more information.
144 usage="%prog [options] %{sVv}"
145 parser = optparse.OptionParser(description=description,
146 usage=usage,
147 add_help_option=True,
148 version=__version__)
150 parser.add_option("-C", "--category", dest='category', metavar="CAT",
151 help=textwrap.dedent("""\
152 Category for change. This becomes the Change.category attribute, which
153 can be used within the buildmaster to filter changes.
154 """))
155 parser.add_option("-c", "--cvsroot", dest='cvsroot', metavar="PATH",
156 help=textwrap.dedent("""\
157 CVSROOT for use by buildbot slaves to checkout code.
158 This becomes the Change.repository attribute.
159 Exmaple: :ext:myhost:/cvsroot
160 """))
161 parser.add_option("-e", "--email", dest='email', metavar="EMAIL",
162 help=textwrap.dedent("""\
163 Email address of the buildbot.
164 """))
165 parser.add_option("-f", "--fromhost", dest='fromhost', metavar="HOST",
166 help=textwrap.dedent("""\
167 The hostname that email messages appear to be coming from. The From:
168 header of the outgoing message will look like user@hostname. By
169 default, hostname is the machine's fully qualified domain name.
170 """))
171 parser.add_option("-m", "--mailhost", dest='mailhost', metavar="HOST",
172 default="localhost",
173 help=textwrap.dedent("""\
174 The hostname of an available SMTP server. The default is
175 'localhost'.
176 """))
177 parser.add_option("--mailport", dest='mailport', metavar="PORT",
178 default=25, type="int",
179 help=textwrap.dedent("""\
180 The port number of SMTP server. The default is '25'.
181 """))
182 parser.add_option("-q", "--quiet", dest='verbose', action="store_false",
183 default=True,
184 help=textwrap.dedent("""\
185 Don't print as much status to stdout.
186 """))
187 parser.add_option("-p", "--path", dest='path', metavar="PATH",
188 help=textwrap.dedent("""\
189 The path for the files in this update. This comes from the %p parameter
190 in loginfo for CVS version 1.12.x. Do not use this for CVS version 1.11.x
191 """))
192 parser.add_option("-P", "--project", dest='project', metavar="PROJ",
193 help=textwrap.dedent("""\
194 The project for the source. Often set to the CVS module being modified. This becomes
195 the Change.project attribute.
196 """))
197 parser.add_option("-R", "--reply-to", dest='replyto', metavar="ADDR",
198 help=textwrap.dedent("""\
199 Add a "Reply-To: ADDR" header to the email message.
200 """))
201 parser.add_option("-t", "--testing", action="store_true", dest="amTesting", default=False)
202 parser.set_defaults(smtplib=smtplib)
204 def get_options():
205 options, args = parser.parse_args()
207 # rest of command line are the files.
208 options.files = args
209 if options.path is None:
210 options.cvsmode = '1.11'
211 else:
212 options.cvsmode = '1.12'
214 if options.cvsroot is None:
215 parser.error('--cvsroot is required')
216 if options.email is None:
217 parser.error('--email is required')
219 # set up for unit tests
220 if options.amTesting:
221 options.verbose = 0
222 options.smtplib = SmtplibMock
224 return options
226 # scan args for options
227 def main():
228 options = get_options()
230 if options.verbose:
231 print 'Mailing %s...' % options.email
232 print 'Generating notification message...'
233 if options.amTesting:
234 send_mail(options)
235 else:
236 fork_and_send_mail(options)
238 if options.verbose:
239 print 'Generating notification message... done.'
240 return 0
242 if __name__ == '__main__':
243 ret = main()
244 sys.exit(ret)