##// END OF EJS Templates
bugzilla: add XMLRPC interface....
Jim Hague -
r13801:60256f7f default
parent child Browse files
Show More
@@ -1,6 +1,7 b''
1 # bugzilla.py - bugzilla integration for mercurial
1 # bugzilla.py - bugzilla integration for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2011 Jim Hague <jim.hague@acm.org>
4 #
5 #
5 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
@@ -8,56 +9,43 b''
8 '''hooks for integrating with the Bugzilla bug tracker
9 '''hooks for integrating with the Bugzilla bug tracker
9
10
10 This hook extension adds comments on bugs in Bugzilla when changesets
11 This hook extension adds comments on bugs in Bugzilla when changesets
11 that refer to bugs by Bugzilla ID are seen. The hook does not change
12 that refer to bugs by Bugzilla ID are seen. The comment is formatted using
12 bug status.
13 the Mercurial template mechanism.
13
14
14 The hook updates the Bugzilla database directly. Only Bugzilla
15 The hook does not change bug status.
15 installations using MySQL are supported.
16
16
17 The hook relies on a Bugzilla script to send bug change notification
17 Two basic modes of access to Bugzilla are provided:
18 emails. That script changes between Bugzilla versions; the
19 'processmail' script used prior to 2.18 is replaced in 2.18 and
20 subsequent versions by 'config/sendbugmail.pl'. Note that these will
21 be run by Mercurial as the user pushing the change; you will need to
22 ensure the Bugzilla install file permissions are set appropriately.
23
18
24 The extension is configured through three different configuration
19 1. Access via the Bugzilla XMLRPC interface (requires Bugzilla 3.4 or later).
25 sections. These keys are recognized in the [bugzilla] section:
26
27 host
28 Hostname of the MySQL server holding the Bugzilla database.
29
20
30 db
21 2. Writing directly to the Bugzilla database. Only Bugzilla installations
31 Name of the Bugzilla database in MySQL. Default 'bugs'.
22 using MySQL are supported. Requires Python MySQLdb.
32
33 user
34 Username to use to access MySQL server. Default 'bugs'.
35
23
36 password
24 Writing directly to the database is susceptible to schema changes, and
37 Password to use to access MySQL server.
25 relies on a Bugzilla contrib script to send out bug change
38
26 notification emails. This script runs as the user running Mercurial,
39 timeout
27 must be run on the host with the Bugzilla install, and requires
40 Database connection timeout (seconds). Default 5.
28 permission to read Bugzilla configuration details and the necessary
41
29 MySQL user and password to have full access rights to the Bugzilla
42 version
30 database. For these reasons this access mode is now considered
43 Bugzilla version. Specify '3.0' for Bugzilla versions 3.0 and later,
31 deprecated, and will not be updated for new Bugzilla versions going
44 '2.18' for Bugzilla versions from 2.18 and '2.16' for versions prior
32 forward.
45 to 2.18.
46
33
47 bzuser
34 Access via XMLRPC needs a Bugzilla username and password to be specified
48 Fallback Bugzilla user name to record comments with, if changeset
35 in the configuration. Comments are added under that username. Since the
49 committer cannot be found as a Bugzilla user.
36 configuration must be readable by all Mercurial users, it is recommended
37 that the rights of that user are restricted in Bugzilla to the minimum
38 necessary to add comments.
39
40 Configuration items common to both access modes:
50
41
51 bzdir
42 [bugzilla]
52 Bugzilla install directory. Used by default notify. Default
43 version
53 '/var/www/html/bugzilla'.
44 This access type to use. Values recognised are:
54
45 xmlrpc Bugzilla XMLRPC interface.
55 notify
46 3.0 MySQL access, Bugzilla 3.0 and later.
56 The command to run to get Bugzilla to send bug change notification
47 2.18 MySQL access, Bugzilla 2.18 and up to but not including 3.0.
57 emails. Substitutes from a map with 3 keys, 'bzdir', 'id' (bug id)
48 2.16 MySQL access, Bugzilla 2.16 and up to but not including 2.18.
58 and 'user' (committer bugzilla email). Default depends on version;
59 from 2.18 it is "cd %(bzdir)s && perl -T contrib/sendbugmail.pl
60 %(id)s %(user)s".
61
49
62 regexp
50 regexp
63 Regular expression to match bug IDs in changeset commit message.
51 Regular expression to match bug IDs in changeset commit message.
@@ -82,23 +70,72 b' template'
82 'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}'
70 'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}'
83
71
84 strip
72 strip
85 The number of slashes to strip from the front of {root} to produce
73 The number of path separator characters to strip from the front of the
86 {webroot}. Default 0.
74 Mercurial repository path ('{root}' in templates) to produce '{webroot}'.
75 For example, a repository with '{root}' '/var/local/my-project' with a
76 strip of 2 gives a value for '{webroot}' of 'my-project'. Default 0.
77
78 [web]
79 baseurl
80 Base URL for browsing Mercurial repositories. Referenced from
81 templates as {hgweb}.
82
83 XMLRPC access mode configuration:
84
85 [bugzilla]
86 bzurl
87 The base URL for the Bugzilla installation.
88 Default 'http://localhost/bugzilla'.
89
90 user
91 The username to use to log into Bugzilla via XMLRPC. Default 'bugs'.
92
93 password
94 The password for Bugzilla login.
95
96 MySQL access mode configuration:
97
98 [bugzilla]
99 host
100 Hostname of the MySQL server holding the Bugzilla database.
101 Default 'localhost'.
102
103 db
104 Name of the Bugzilla database in MySQL. Default 'bugs'.
105
106 user
107 Username to use to access MySQL server. Default 'bugs'.
108
109 password
110 Password to use to access MySQL server.
111
112 timeout
113 Database connection timeout (seconds). Default 5.
114
115 bzuser
116 Fallback Bugzilla user name to record comments with, if changeset
117 committer cannot be found as a Bugzilla user.
118
119 bzdir
120 Bugzilla install directory. Used by default notify. Default
121 '/var/www/html/bugzilla'.
122
123 notify
124 The command to run to get Bugzilla to send bug change notification
125 emails. Substitutes from a map with 3 keys, 'bzdir', 'id' (bug id)
126 and 'user' (committer bugzilla email). Default depends on version;
127 from 2.18 it is "cd %(bzdir)s && perl -T contrib/sendbugmail.pl
128 %(id)s %(user)s".
87
129
88 usermap
130 usermap
89 Path of file containing Mercurial committer ID to Bugzilla user ID
131 Path of file containing Mercurial committer ID to Bugzilla user ID
90 mappings. If specified, the file should contain one mapping per
132 mappings. If specified, the file should contain one mapping per
91 line, "committer"="Bugzilla user". See also the [usermap] section.
133 line, "committer"="Bugzilla user". See also the [usermap] section.
92
134
135 [usermap]
93 The [usermap] section is used to specify mappings of Mercurial
136 The [usermap] section is used to specify mappings of Mercurial
94 committer ID to Bugzilla user ID. See also [bugzilla].usermap.
137 committer email to Bugzilla user email. See also [bugzilla].usermap.
95 "committer"="Bugzilla user"
138 Contains entries of the form "committer"="Bugzilla user".
96
97 Finally, the [web] section supports one entry:
98
99 baseurl
100 Base URL for browsing Mercurial repositories. Reference from
101 templates as {hgweb}.
102
139
103 Activating the extension::
140 Activating the extension::
104
141
@@ -109,11 +146,27 b' Activating the extension::'
109 # run bugzilla hook on every change pulled or pushed in here
146 # run bugzilla hook on every change pulled or pushed in here
110 incoming.bugzilla = python:hgext.bugzilla.hook
147 incoming.bugzilla = python:hgext.bugzilla.hook
111
148
112 Example configuration:
149 Example configurations:
150
151 XMLRPC example configuration. This uses the Bugzilla at
152 'http://my-project.org/bugzilla', logging in as user 'bugmail@my-project.org'
153 wityh password 'plugh'. It is used with a collection of Mercurial
154 repositories in '/var/local/hg/repos/'. ::
113
155
114 This example configuration is for a collection of Mercurial
156 [bugzilla]
115 repositories in /var/local/hg/repos/ used with a local Bugzilla 3.2
157 bzurl=http://my-project.org/bugzilla
116 installation in /opt/bugzilla-3.2. ::
158 user=bugmail@my-project.org
159 password=plugh
160 version=xmlrpc
161
162 [web]
163 baseurl=http://my-project.org/hg
164
165 MySQL example configuration. This is for a collection of Mercurial
166 repositories in '/var/local/hg/repos/' used with a local Bugzilla 3.2
167 installation in /opt/bugzilla-3.2. The MySQL database is on 'localhost',
168 the Bugzilla database name is 'bugs' and MySQL is accessed with MySQL
169 username 'bugs' password 'XYZZY'. ::
117
170
118 [bugzilla]
171 [bugzilla]
119 host=localhost
172 host=localhost
@@ -132,7 +185,7 b' installation in /opt/bugzilla-3.2. ::'
132 [usermap]
185 [usermap]
133 user@emaildomain.com=user.name@bugzilladomain.com
186 user@emaildomain.com=user.name@bugzilladomain.com
134
187
135 Commits add a comment to the Bugzilla bug record of the form::
188 Both the above add a comment to the Bugzilla bug record of the form::
136
189
137 Changeset 3b16791d6642 in repository-name.
190 Changeset 3b16791d6642 in repository-name.
138 http://dev.domain.com/hg/repository-name/rev/3b16791d6642
191 http://dev.domain.com/hg/repository-name/rev/3b16791d6642
@@ -143,7 +196,7 b' Commits add a comment to the Bugzilla bu'
143 from mercurial.i18n import _
196 from mercurial.i18n import _
144 from mercurial.node import short
197 from mercurial.node import short
145 from mercurial import cmdutil, templater, util
198 from mercurial import cmdutil, templater, util
146 import re, time
199 import re, time, xmlrpclib
147
200
148 class bzaccess(object):
201 class bzaccess(object):
149 '''Base class for access to Bugzilla.'''
202 '''Base class for access to Bugzilla.'''
@@ -187,6 +240,9 b' class bzmysql(bzaccess):'
187 '''Support for direct MySQL access to Bugzilla.
240 '''Support for direct MySQL access to Bugzilla.
188
241
189 The earliest Bugzilla version this is tested with is version 2.16.
242 The earliest Bugzilla version this is tested with is version 2.16.
243
244 If your Bugzilla is version 3.2 or above, you are strongly
245 recommended to use the XMLRPC access method instead.
190 '''
246 '''
191
247
192 @staticmethod
248 @staticmethod
@@ -301,7 +357,7 b' class bzmysql(bzaccess):'
301 return userid
357 return userid
302
358
303 def get_bugzilla_user(self, committer):
359 def get_bugzilla_user(self, committer):
304 '''see if committer is a registered bugzilla user. Return
360 '''See if committer is a registered bugzilla user. Return
305 bugzilla username and userid if so. If not, return default
361 bugzilla username and userid if so. If not, return default
306 bugzilla username and userid.'''
362 bugzilla username and userid.'''
307 user = self.map_committer(committer)
363 user = self.map_committer(committer)
@@ -356,13 +412,122 b' class bzmysql_3_0(bzmysql_2_18):'
356 raise util.Abort(_('unknown database schema'))
412 raise util.Abort(_('unknown database schema'))
357 return ids[0][0]
413 return ids[0][0]
358
414
415 # Buzgilla via XMLRPC interface.
416
417 class CookieSafeTransport(xmlrpclib.SafeTransport):
418 """A SafeTransport that retains cookies over its lifetime.
419
420 The regular xmlrpclib transports ignore cookies. Which causes
421 a bit of a problem when you need a cookie-based login, as with
422 the Bugzilla XMLRPC interface.
423
424 So this is a SafeTransport which looks for cookies being set
425 in responses and saves them to add to all future requests.
426 It appears a SafeTransport can do both HTTP and HTTPS sessions,
427 which saves us having to do a CookieTransport too.
428 """
429
430 # Inspiration drawn from
431 # http://blog.godson.in/2010/09/how-to-make-python-xmlrpclib-client.html
432 # http://www.itkovian.net/base/transport-class-for-pythons-xml-rpc-lib/
433
434 cookies = []
435 def send_cookies(self, connection):
436 if self.cookies:
437 for cookie in self.cookies:
438 connection.putheader("Cookie", cookie)
439
440 def request(self, host, handler, request_body, verbose=0):
441 self.verbose = verbose
442
443 # issue XML-RPC request
444 h = self.make_connection(host)
445 if verbose:
446 h.set_debuglevel(1)
447
448 self.send_request(h, handler, request_body)
449 self.send_host(h, host)
450 self.send_cookies(h)
451 self.send_user_agent(h)
452 self.send_content(h, request_body)
453
454 # Deal with differences between Python 2.4-2.6 and 2.7.
455 # In the former h is a HTTP(S). In the latter it's a
456 # HTTP(S)Connection. Luckily, the 2.4-2.6 implementation of
457 # HTTP(S) has an underlying HTTP(S)Connection, so extract
458 # that and use it.
459 try:
460 response = h.getresponse()
461 except AttributeError:
462 response = h._conn.getresponse()
463
464 # Add any cookie definitions to our list.
465 for header in response.msg.getallmatchingheaders("Set-Cookie"):
466 val = header.split(": ", 1)[1]
467 cookie = val.split(";", 1)[0]
468 self.cookies.append(cookie)
469
470 if response.status != 200:
471 raise xmlrpclib.ProtocolError(host + handler, response.status,
472 response.reason, response.msg.headers)
473
474 payload = response.read()
475 parser, unmarshaller = self.getparser()
476 parser.feed(payload)
477 parser.close()
478
479 return unmarshaller.close()
480
481 class bzxmlrpc(bzaccess):
482 """Support for access to Bugzilla via the Bugzilla XMLRPC API.
483
484 Requires a minimum Bugzilla version 3.4.
485 """
486
487 def __init__(self, ui):
488 bzaccess.__init__(self, ui)
489
490 bzweb = self.ui.config('bugzilla', 'bzurl',
491 'http://localhost/bugzilla/')
492 bzweb = bzweb.rstrip("/") + "/xmlrpc.cgi"
493
494 user = self.ui.config('bugzilla', 'user', 'bugs')
495 passwd = self.ui.config('bugzilla', 'password')
496
497 self.bzproxy = xmlrpclib.ServerProxy(bzweb, CookieSafeTransport())
498 self.bzproxy.User.login(dict(login=user, password=passwd))
499
500 def get_bug_comments(self, id):
501 """Return a string with all comment text for a bug."""
502 c = self.bzproxy.Bug.comments(dict(ids=[id]))
503 return ''.join([t['text'] for t in c['bugs'][str(id)]['comments']])
504
505 def filter_real_bug_ids(self, ids):
506 res = set()
507 bugs = self.bzproxy.Bug.get(dict(ids=sorted(ids), permissive=True))
508 for bug in bugs['bugs']:
509 res.add(bug['id'])
510 return res
511
512 def filter_cset_known_bug_ids(self, node, ids):
513 for id in sorted(ids):
514 if self.get_bug_comments(id).find(short(node)) != -1:
515 self.ui.status(_('bug %d already knows about changeset %s\n') %
516 (id, short(node)))
517 ids.discard(id)
518 return ids
519
520 def add_comment(self, bugid, text, committer):
521 self.bzproxy.Bug.add_comment(dict(id=bugid, comment=text))
522
359 class bugzilla(object):
523 class bugzilla(object):
360 # supported versions of bugzilla. different versions have
524 # supported versions of bugzilla. different versions have
361 # different schemas.
525 # different schemas.
362 _versions = {
526 _versions = {
363 '2.16': bzmysql,
527 '2.16': bzmysql,
364 '2.18': bzmysql_2_18,
528 '2.18': bzmysql_2_18,
365 '3.0': bzmysql_3_0
529 '3.0': bzmysql_3_0,
530 'xmlrpc': bzxmlrpc
366 }
531 }
367
532
368 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
533 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
General Comments 0
You need to be logged in to leave comments. Login now