comparison hgext/bugzilla.py @ 7618:6c89dd0a7797

Bugzilla 2.18 and on use contrib/sendbugmail.pl, not processmail. During 2.17, Bugzilla ditched the old 'processmail' script. With 2.18 contrib/sendbugmail.pl arrived in its place. For notification emails to work properly, sendbugmail.pl requires as its second parameter the Bugzilla user who made the commit. Otherwise the user will not be recognised as the committer, and will receive notification emails about the commit regardless of their preference about being notified on their own commits. This parameter should be given to processmail also, but wasn't for historical reasons. Add new config with the local Bugzilla install directory, and provide defaults for the notify string which should work for most setups. Still permit notify string to be specified, and for backwards compatibility with any extant notify strings try first interpolating notify string with old-style single bug ID argument. Add new 2.18 support version to introduce sendbugmail.pl. In other words, this update should be backwards-compatible with existing installations, but offers simplified setup in most cases. And as a bonus Bugzilla notification emails will be dispatched correctly; notifiers will not receive an email unless configured to do so.
author Jim Hague <jim.hague@acm.org>
date Fri, 09 Jan 2009 22:15:08 +0000
parents d8cd79fbed3c
children fece056bf240
comparison
equal deleted inserted replaced
7617:7b554c6ad390 7618:6c89dd0a7797
27 host Hostname of the MySQL server holding the Bugzilla database. 27 host Hostname of the MySQL server holding the Bugzilla database.
28 db Name of the Bugzilla database in MySQL. Default 'bugs'. 28 db Name of the Bugzilla database in MySQL. Default 'bugs'.
29 user Username to use to access MySQL server. Default 'bugs'. 29 user Username to use to access MySQL server. Default 'bugs'.
30 password Password to use to access MySQL server. 30 password Password to use to access MySQL server.
31 timeout Database connection timeout (seconds). Default 5. 31 timeout Database connection timeout (seconds). Default 5.
32 version Bugzilla version. Specify '3.0' for Bugzilla versions from 32 version Bugzilla version. Specify '3.0' for Bugzilla versions 3.0 and
33 3.0 onwards, and '2.16' for versions prior to 3.0. 33 later, '2.18' for Bugzilla versions from 2.18 and '2.16' for
34 versions prior to 2.18.
34 bzuser Fallback Bugzilla user name to record comments with, if 35 bzuser Fallback Bugzilla user name to record comments with, if
35 changeset committer cannot be found as a Bugzilla user. 36 changeset committer cannot be found as a Bugzilla user.
37 bzdir Bugzilla install directory. Used by default notify.
38 Default '/var/www/html/bugzilla'.
36 notify The command to run to get Bugzilla to send bug change 39 notify The command to run to get Bugzilla to send bug change
37 notification emails. Substitutes one string parameter, 40 notification emails. Substitutes from a map with 3 keys,
38 the bug ID. Default 'cd /var/www/html/bugzilla && ' 41 'bzdir', 'id' (bug id) and 'user' (committer bugzilla email).
39 './processmail %s nobody@nowhere.com'. 42 Default depends on version; from 2.18 it is
43 "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s".
40 regexp Regular expression to match bug IDs in changeset commit message. 44 regexp Regular expression to match bug IDs in changeset commit message.
41 Must contain one "()" group. The default expression matches 45 Must contain one "()" group. The default expression matches
42 'Bug 1234', 'Bug no. 1234', 'Bug number 1234', 46 'Bug 1234', 'Bug no. 1234', 'Bug number 1234',
43 'Bugs 1234,5678', 'Bug 1234 and 5678' and variations thereof. 47 'Bugs 1234,5678', 'Bug 1234 and 5678' and variations thereof.
44 Matching is case insensitive. 48 Matching is case insensitive.
86 [bugzilla] 90 [bugzilla]
87 host=localhost 91 host=localhost
88 password=XYZZY 92 password=XYZZY
89 version=3.0 93 version=3.0
90 bzuser=unknown@domain.com 94 bzuser=unknown@domain.com
91 notify=cd /opt/bugzilla-3.2 && perl -T contrib/sendbugmail.pl %%s bugmail@domain.com 95 bzdir=/opt/bugzilla-3.2
92 template=Changeset {node|short} in {root|basename}.\\n{hgweb}/{webroot}/rev/{node|short}\\n\\n{desc}\\n 96 template=Changeset {node|short} in {root|basename}.\\n{hgweb}/{webroot}/rev/{node|short}\\n\\n{desc}\\n
93 strip=5 97 strip=5
94 98
95 [web] 99 [web]
96 baseurl=http://dev.domain.com/hg 100 baseurl=http://dev.domain.com/hg
134 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd, 138 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
135 db=db, connect_timeout=timeout) 139 db=db, connect_timeout=timeout)
136 self.cursor = self.conn.cursor() 140 self.cursor = self.conn.cursor()
137 self.longdesc_id = self.get_longdesc_id() 141 self.longdesc_id = self.get_longdesc_id()
138 self.user_ids = {} 142 self.user_ids = {}
143 self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
139 144
140 def run(self, *args, **kwargs): 145 def run(self, *args, **kwargs):
141 '''run a query.''' 146 '''run a query.'''
142 self.ui.note(_('query: %s %s\n') % (args, kwargs)) 147 self.ui.note(_('query: %s %s\n') % (args, kwargs))
143 try: 148 try:
170 self.ui.status(_('bug %d already knows about changeset %s\n') % 175 self.ui.status(_('bug %d already knows about changeset %s\n') %
171 (id, short(node))) 176 (id, short(node)))
172 unknown.pop(id, None) 177 unknown.pop(id, None)
173 return util.sort(unknown.keys()) 178 return util.sort(unknown.keys())
174 179
175 def notify(self, ids): 180 def notify(self, ids, committer):
176 '''tell bugzilla to send mail.''' 181 '''tell bugzilla to send mail.'''
177 182
178 self.ui.status(_('telling bugzilla to send mail:\n')) 183 self.ui.status(_('telling bugzilla to send mail:\n'))
184 (user, userid) = self.get_bugzilla_user(committer)
179 for id in ids: 185 for id in ids:
180 self.ui.status(_(' bug %s\n') % id) 186 self.ui.status(_(' bug %s\n') % id)
181 cmd = self.ui.config('bugzilla', 'notify', 187 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
182 'cd /var/www/html/bugzilla && ' 188 bzdir = self.ui.config('bugzilla', 'bzdir', '/var/www/html/bugzilla')
183 './processmail %s nobody@nowhere.com') % id 189 try:
190 # Backwards-compatible with old notify string, which
191 # took one string. This will throw with a new format
192 # string.
193 cmd = cmdfmt % id
194 except TypeError:
195 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
196 self.ui.note(_('running notify command %s\n') % cmd)
184 fp = util.popen('(%s) 2>&1' % cmd) 197 fp = util.popen('(%s) 2>&1' % cmd)
185 out = fp.read() 198 out = fp.read()
186 ret = fp.close() 199 ret = fp.close()
187 if ret: 200 if ret:
188 self.ui.warn(out) 201 self.ui.warn(out)
213 for committer, bzuser in self.ui.configitems('usermap'): 226 for committer, bzuser in self.ui.configitems('usermap'):
214 if committer.lower() == user.lower(): 227 if committer.lower() == user.lower():
215 return bzuser 228 return bzuser
216 return user 229 return user
217 230
218 def add_comment(self, bugid, text, committer): 231 def get_bugzilla_user(self, committer):
219 '''add comment to bug. try adding comment as committer of 232 '''see if committer is a registered bugzilla user. Return
220 changeset, otherwise as default bugzilla user.''' 233 bugzilla username and userid if so. If not, return default
234 bugzilla username and userid.'''
221 user = self.map_committer(committer) 235 user = self.map_committer(committer)
222 try: 236 try:
223 userid = self.get_user_id(user) 237 userid = self.get_user_id(user)
224 except KeyError: 238 except KeyError:
225 try: 239 try:
226 defaultuser = self.ui.config('bugzilla', 'bzuser') 240 defaultuser = self.ui.config('bugzilla', 'bzuser')
227 if not defaultuser: 241 if not defaultuser:
228 raise util.Abort(_('cannot find bugzilla user id for %s') % 242 raise util.Abort(_('cannot find bugzilla user id for %s') %
229 user) 243 user)
230 userid = self.get_user_id(defaultuser) 244 userid = self.get_user_id(defaultuser)
245 user = defaultuser
231 except KeyError: 246 except KeyError:
232 raise util.Abort(_('cannot find bugzilla user id for %s or %s') % 247 raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
233 (user, defaultuser)) 248 (user, defaultuser))
249 return (user, userid)
250
251 def add_comment(self, bugid, text, committer):
252 '''add comment to bug. try adding comment as committer of
253 changeset, otherwise as default bugzilla user.'''
254 (user, userid) = self.get_bugzilla_user(committer)
234 now = time.strftime('%Y-%m-%d %H:%M:%S') 255 now = time.strftime('%Y-%m-%d %H:%M:%S')
235 self.run('''insert into longdescs 256 self.run('''insert into longdescs
236 (bug_id, who, bug_when, thetext) 257 (bug_id, who, bug_when, thetext)
237 values (%s, %s, %s, %s)''', 258 values (%s, %s, %s, %s)''',
238 (bugid, userid, now, text)) 259 (bugid, userid, now, text))
239 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid) 260 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
240 values (%s, %s, %s, %s)''', 261 values (%s, %s, %s, %s)''',
241 (bugid, userid, now, self.longdesc_id)) 262 (bugid, userid, now, self.longdesc_id))
242 self.conn.commit() 263 self.conn.commit()
243 264
244 class bugzilla_3_0(bugzilla_2_16): 265 class bugzilla_2_18(bugzilla_2_16):
245 '''support for bugzilla 3.0 series.''' 266 '''support for bugzilla 2.18 series.'''
246 267
247 def __init__(self, ui): 268 def __init__(self, ui):
248 bugzilla_2_16.__init__(self, ui) 269 bugzilla_2_16.__init__(self, ui)
270 self.default_notify = "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
271
272 class bugzilla_3_0(bugzilla_2_18):
273 '''support for bugzilla 3.0 series.'''
274
275 def __init__(self, ui):
276 bugzilla_2_18.__init__(self, ui)
249 277
250 def get_longdesc_id(self): 278 def get_longdesc_id(self):
251 '''get identity of longdesc field''' 279 '''get identity of longdesc field'''
252 self.run('select id from fielddefs where name = "longdesc"') 280 self.run('select id from fielddefs where name = "longdesc"')
253 ids = self.cursor.fetchall() 281 ids = self.cursor.fetchall()
258 class bugzilla(object): 286 class bugzilla(object):
259 # supported versions of bugzilla. different versions have 287 # supported versions of bugzilla. different versions have
260 # different schemas. 288 # different schemas.
261 _versions = { 289 _versions = {
262 '2.16': bugzilla_2_16, 290 '2.16': bugzilla_2_16,
291 '2.18': bugzilla_2_18,
263 '3.0': bugzilla_3_0 292 '3.0': bugzilla_3_0
264 } 293 }
265 294
266 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*' 295 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
267 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)') 296 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
373 ctx = repo[node] 402 ctx = repo[node]
374 ids = bz.find_bug_ids(ctx) 403 ids = bz.find_bug_ids(ctx)
375 if ids: 404 if ids:
376 for id in ids: 405 for id in ids:
377 bz.update(id, ctx) 406 bz.update(id, ctx)
378 bz.notify(ids) 407 bz.notify(ids, util.email(ctx.user()))
379 except MySQLdb.MySQLError, err: 408 except MySQLdb.MySQLError, err:
380 raise util.Abort(_('database error: %s') % err[1]) 409 raise util.Abort(_('database error: %s') % err[1])
381 410