Show More
@@ -15,14 +15,16 b' the Mercurial template mechanism.' | |||
|
15 | 15 | The bug references can optionally include an update for Bugzilla of the |
|
16 | 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 | 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 | 28 | using MySQL are supported. Requires Python MySQLdb. |
|
27 | 29 | |
|
28 | 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 | 52 | Bugzilla is used instead as the source of the comment. Marking bugs fixed |
|
51 | 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 | 59 | Configuration items common to all access modes: |
|
54 | 60 | |
|
55 | 61 | bugzilla.version |
|
56 | 62 | The access type to use. Values recognized are: |
|
57 | 63 | |
|
64 | :``restapi``: Bugzilla REST-API, Bugzilla 5.0 and later. | |
|
58 | 65 | :``xmlrpc``: Bugzilla XMLRPC interface. |
|
59 | 66 | :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces. |
|
60 | 67 | :``3.0``: MySQL access, Bugzilla 3.0 and later. |
@@ -135,7 +142,7 b' The ``[usermap]`` section is used to spe' | |||
|
135 | 142 | committer email to Bugzilla user email. See also ``bugzilla.usermap``. |
|
136 | 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 | 147 | bugzilla.bzurl |
|
141 | 148 | The base URL for the Bugzilla installation. |
@@ -148,6 +155,13 b' bugzilla.user' | |||
|
148 | 155 | bugzilla.password |
|
149 | 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 | 165 | XMLRPC+email access mode uses the XMLRPC access mode configuration items, |
|
152 | 166 | and also: |
|
153 | 167 | |
@@ -279,6 +293,7 b' All the above add a comment to the Bugzi' | |||
|
279 | 293 | |
|
280 | 294 | from __future__ import absolute_import |
|
281 | 295 | |
|
296 | import json | |
|
282 | 297 | import re |
|
283 | 298 | import time |
|
284 | 299 | |
@@ -288,6 +303,7 b' from mercurial import (' | |||
|
288 | 303 | cmdutil, |
|
289 | 304 | error, |
|
290 | 305 | mail, |
|
306 | url, | |
|
291 | 307 | util, |
|
292 | 308 | ) |
|
293 | 309 | |
@@ -773,6 +789,136 b' class bzxmlrpcemail(bzxmlrpc):' | |||
|
773 | 789 | cmds.append(self.makecommandline("resolution", self.fixresolution)) |
|
774 | 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 | 922 | class bugzilla(object): |
|
777 | 923 | # supported versions of bugzilla. different versions have |
|
778 | 924 | # different schemas. |
@@ -781,7 +927,8 b' class bugzilla(object):' | |||
|
781 | 927 | '2.18': bzmysql_2_18, |
|
782 | 928 | '3.0': bzmysql_3_0, |
|
783 | 929 | 'xmlrpc': bzxmlrpc, |
|
784 | 'xmlrpc+email': bzxmlrpcemail | |
|
930 | 'xmlrpc+email': bzxmlrpcemail, | |
|
931 | 'restapi': bzrestapi, | |
|
785 | 932 | } |
|
786 | 933 | |
|
787 | 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