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 |
|
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 |
|
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 |
|
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 |
|
|
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 |
''' |
|
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