Show More
@@ -15,14 +15,16 b' the Mercurial template mechanism.' | |||||
15 | The bug references can optionally include an update for Bugzilla of the |
|
15 | The bug references can optionally include an update for Bugzilla of the | |
16 | hours spent working on the bug. Bugs can also be marked fixed. |
|
16 | hours spent working on the bug. Bugs can also be marked fixed. | |
17 |
|
17 | |||
18 |
|
|
18 | Four basic modes of access to Bugzilla are provided: | |
|
19 | ||||
|
20 | 1. Access via the Bugzilla REST-API. Requires bugzilla 5.0 or later. | |||
19 |
|
21 | |||
20 |
|
|
22 | 2. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later. | |
21 |
|
23 | |||
22 |
|
|
24 | 3. Check data via the Bugzilla XMLRPC interface and submit bug change | |
23 | via email to Bugzilla email interface. Requires Bugzilla 3.4 or later. |
|
25 | via email to Bugzilla email interface. Requires Bugzilla 3.4 or later. | |
24 |
|
26 | |||
25 |
|
|
27 | 4. Writing directly to the Bugzilla database. Only Bugzilla installations | |
26 | using MySQL are supported. Requires Python MySQLdb. |
|
28 | using MySQL are supported. Requires Python MySQLdb. | |
27 |
|
29 | |||
28 | Writing directly to the database is susceptible to schema changes, and |
|
30 | Writing directly to the database is susceptible to schema changes, and | |
@@ -50,11 +52,16 b' user, the email associated with the Bugz' | |||||
50 | Bugzilla is used instead as the source of the comment. Marking bugs fixed |
|
52 | Bugzilla is used instead as the source of the comment. Marking bugs fixed | |
51 | works on all supported Bugzilla versions. |
|
53 | works on all supported Bugzilla versions. | |
52 |
|
54 | |||
|
55 | Access via the REST-API needs either a Bugzilla username and password | |||
|
56 | or an apikey specified in the configuration. Comments are made under | |||
|
57 | the given username or the user assoicated with the apikey in Bugzilla. | |||
|
58 | ||||
53 | Configuration items common to all access modes: |
|
59 | Configuration items common to all access modes: | |
54 |
|
60 | |||
55 | bugzilla.version |
|
61 | bugzilla.version | |
56 | The access type to use. Values recognized are: |
|
62 | The access type to use. Values recognized are: | |
57 |
|
63 | |||
|
64 | :``restapi``: Bugzilla REST-API, Bugzilla 5.0 and later. | |||
58 | :``xmlrpc``: Bugzilla XMLRPC interface. |
|
65 | :``xmlrpc``: Bugzilla XMLRPC interface. | |
59 | :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces. |
|
66 | :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces. | |
60 | :``3.0``: MySQL access, Bugzilla 3.0 and later. |
|
67 | :``3.0``: MySQL access, Bugzilla 3.0 and later. | |
@@ -135,7 +142,7 b' The ``[usermap]`` section is used to spe' | |||||
135 | committer email to Bugzilla user email. See also ``bugzilla.usermap``. |
|
142 | committer email to Bugzilla user email. See also ``bugzilla.usermap``. | |
136 | Contains entries of the form ``committer = Bugzilla user``. |
|
143 | Contains entries of the form ``committer = Bugzilla user``. | |
137 |
|
144 | |||
138 | XMLRPC access mode configuration: |
|
145 | XMLRPC and REST-API access mode configuration: | |
139 |
|
146 | |||
140 | bugzilla.bzurl |
|
147 | bugzilla.bzurl | |
141 | The base URL for the Bugzilla installation. |
|
148 | The base URL for the Bugzilla installation. | |
@@ -148,6 +155,13 b' bugzilla.user' | |||||
148 | bugzilla.password |
|
155 | bugzilla.password | |
149 | The password for Bugzilla login. |
|
156 | The password for Bugzilla login. | |
150 |
|
157 | |||
|
158 | REST-API access mode uses the options listed above as well as: | |||
|
159 | ||||
|
160 | bugzilla.apikey | |||
|
161 | An apikey generated on the Bugzilla instance for api access. | |||
|
162 | Using an apikey removes the need to store the user and password | |||
|
163 | options. | |||
|
164 | ||||
151 | XMLRPC+email access mode uses the XMLRPC access mode configuration items, |
|
165 | XMLRPC+email access mode uses the XMLRPC access mode configuration items, | |
152 | and also: |
|
166 | and also: | |
153 |
|
167 | |||
@@ -279,6 +293,7 b' All the above add a comment to the Bugzi' | |||||
279 |
|
293 | |||
280 | from __future__ import absolute_import |
|
294 | from __future__ import absolute_import | |
281 |
|
295 | |||
|
296 | import json | |||
282 | import re |
|
297 | import re | |
283 | import time |
|
298 | import time | |
284 |
|
299 | |||
@@ -288,6 +303,7 b' from mercurial import (' | |||||
288 | cmdutil, |
|
303 | cmdutil, | |
289 | error, |
|
304 | error, | |
290 | mail, |
|
305 | mail, | |
|
306 | url, | |||
291 | util, |
|
307 | util, | |
292 | ) |
|
308 | ) | |
293 |
|
309 | |||
@@ -773,6 +789,136 b' class bzxmlrpcemail(bzxmlrpc):' | |||||
773 | cmds.append(self.makecommandline("resolution", self.fixresolution)) |
|
789 | cmds.append(self.makecommandline("resolution", self.fixresolution)) | |
774 | self.send_bug_modify_email(bugid, cmds, text, committer) |
|
790 | self.send_bug_modify_email(bugid, cmds, text, committer) | |
775 |
|
791 | |||
|
792 | class NotFound(LookupError): | |||
|
793 | pass | |||
|
794 | ||||
|
795 | class bzrestapi(bzaccess): | |||
|
796 | """Read and write bugzilla data using the REST API available since | |||
|
797 | Bugzilla 5.0. | |||
|
798 | """ | |||
|
799 | def __init__(self, ui): | |||
|
800 | bzaccess.__init__(self, ui) | |||
|
801 | bz = self.ui.config('bugzilla', 'bzurl', | |||
|
802 | 'http://localhost/bugzilla/') | |||
|
803 | self.bzroot = '/'.join([bz, 'rest']) | |||
|
804 | self.apikey = self.ui.config('bugzilla', 'apikey', '') | |||
|
805 | self.user = self.ui.config('bugzilla', 'user', 'bugs') | |||
|
806 | self.passwd = self.ui.config('bugzilla', 'password') | |||
|
807 | self.fixstatus = self.ui.config('bugzilla', 'fixstatus', 'RESOLVED') | |||
|
808 | self.fixresolution = self.ui.config('bugzilla', 'fixresolution', | |||
|
809 | 'FIXED') | |||
|
810 | ||||
|
811 | def apiurl(self, targets, include_fields=None): | |||
|
812 | url = '/'.join([self.bzroot] + [str(t) for t in targets]) | |||
|
813 | qv = {} | |||
|
814 | if self.apikey: | |||
|
815 | qv['api_key'] = self.apikey | |||
|
816 | elif self.user and self.passwd: | |||
|
817 | qv['login'] = self.user | |||
|
818 | qv['password'] = self.passwd | |||
|
819 | if include_fields: | |||
|
820 | qv['include_fields'] = include_fields | |||
|
821 | if qv: | |||
|
822 | url = '%s?%s' % (url, util.urlreq.urlencode(qv)) | |||
|
823 | return url | |||
|
824 | ||||
|
825 | def _fetch(self, burl): | |||
|
826 | try: | |||
|
827 | resp = url.open(self.ui, burl) | |||
|
828 | return json.loads(resp.read()) | |||
|
829 | except util.urlerr.httperror as inst: | |||
|
830 | if inst.code == 401: | |||
|
831 | raise error.Abort(_('authorization failed')) | |||
|
832 | if inst.code == 404: | |||
|
833 | raise NotFound() | |||
|
834 | else: | |||
|
835 | raise | |||
|
836 | ||||
|
837 | def _submit(self, burl, data, method='POST'): | |||
|
838 | data = json.dumps(data) | |||
|
839 | if method == 'PUT': | |||
|
840 | class putrequest(util.urlreq.request): | |||
|
841 | def get_method(self): | |||
|
842 | return 'PUT' | |||
|
843 | request_type = putrequest | |||
|
844 | else: | |||
|
845 | request_type = util.urlreq.request | |||
|
846 | req = request_type(burl, data, | |||
|
847 | {'Content-Type': 'application/json'}) | |||
|
848 | try: | |||
|
849 | resp = url.opener(self.ui).open(req) | |||
|
850 | return json.loads(resp.read()) | |||
|
851 | except util.urlerr.httperror as inst: | |||
|
852 | if inst.code == 401: | |||
|
853 | raise error.Abort(_('authorization failed')) | |||
|
854 | if inst.code == 404: | |||
|
855 | raise NotFound() | |||
|
856 | else: | |||
|
857 | raise | |||
|
858 | ||||
|
859 | def filter_real_bug_ids(self, bugs): | |||
|
860 | '''remove bug IDs that do not exist in Bugzilla from bugs.''' | |||
|
861 | badbugs = set() | |||
|
862 | for bugid in bugs: | |||
|
863 | burl = self.apiurl(('bug', bugid), include_fields='status') | |||
|
864 | try: | |||
|
865 | self._fetch(burl) | |||
|
866 | except NotFound: | |||
|
867 | badbugs.add(bugid) | |||
|
868 | for bugid in badbugs: | |||
|
869 | del bugs[bugid] | |||
|
870 | ||||
|
871 | def filter_cset_known_bug_ids(self, node, bugs): | |||
|
872 | '''remove bug IDs where node occurs in comment text from bugs.''' | |||
|
873 | sn = short(node) | |||
|
874 | for bugid in bugs.keys(): | |||
|
875 | burl = self.apiurl(('bug', bugid, 'comment'), include_fields='text') | |||
|
876 | result = self._fetch(burl) | |||
|
877 | comments = result['bugs'][str(bugid)]['comments'] | |||
|
878 | if any(sn in c['text'] for c in comments): | |||
|
879 | self.ui.status(_('bug %d already knows about changeset %s\n') % | |||
|
880 | (bugid, sn)) | |||
|
881 | del bugs[bugid] | |||
|
882 | ||||
|
883 | def updatebug(self, bugid, newstate, text, committer): | |||
|
884 | '''update the specified bug. Add comment text and set new states. | |||
|
885 | ||||
|
886 | If possible add the comment as being from the committer of | |||
|
887 | the changeset. Otherwise use the default Bugzilla user. | |||
|
888 | ''' | |||
|
889 | bugmod = {} | |||
|
890 | if 'hours' in newstate: | |||
|
891 | bugmod['work_time'] = newstate['hours'] | |||
|
892 | if 'fix' in newstate: | |||
|
893 | bugmod['status'] = self.fixstatus | |||
|
894 | bugmod['resolution'] = self.fixresolution | |||
|
895 | if bugmod: | |||
|
896 | # if we have to change the bugs state do it here | |||
|
897 | bugmod['comment'] = { | |||
|
898 | 'comment': text, | |||
|
899 | 'is_private': False, | |||
|
900 | 'is_markdown': False, | |||
|
901 | } | |||
|
902 | burl = self.apiurl(('bug', bugid)) | |||
|
903 | self._submit(burl, bugmod, method='PUT') | |||
|
904 | self.ui.debug('updated bug %s\n' % bugid) | |||
|
905 | else: | |||
|
906 | burl = self.apiurl(('bug', bugid, 'comment')) | |||
|
907 | self._submit(burl, { | |||
|
908 | 'comment': text, | |||
|
909 | 'is_private': False, | |||
|
910 | 'is_markdown': False, | |||
|
911 | }) | |||
|
912 | self.ui.debug('added comment to bug %s\n' % bugid) | |||
|
913 | ||||
|
914 | def notify(self, bugs, committer): | |||
|
915 | '''Force sending of Bugzilla notification emails. | |||
|
916 | ||||
|
917 | Only required if the access method does not trigger notification | |||
|
918 | emails automatically. | |||
|
919 | ''' | |||
|
920 | pass | |||
|
921 | ||||
776 | class bugzilla(object): |
|
922 | class bugzilla(object): | |
777 | # supported versions of bugzilla. different versions have |
|
923 | # supported versions of bugzilla. different versions have | |
778 | # different schemas. |
|
924 | # different schemas. | |
@@ -781,7 +927,8 b' class bugzilla(object):' | |||||
781 | '2.18': bzmysql_2_18, |
|
927 | '2.18': bzmysql_2_18, | |
782 | '3.0': bzmysql_3_0, |
|
928 | '3.0': bzmysql_3_0, | |
783 | 'xmlrpc': bzxmlrpc, |
|
929 | 'xmlrpc': bzxmlrpc, | |
784 | 'xmlrpc+email': bzxmlrpcemail |
|
930 | 'xmlrpc+email': bzxmlrpcemail, | |
|
931 | 'restapi': bzrestapi, | |||
785 | } |
|
932 | } | |
786 |
|
933 | |||
787 | _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*' |
|
934 | _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