8687.15.15
by Karl Fogel
Add the copyright header block to files under lib/lp/bugs/. |
1 |
# Copyright 2009 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
2701.1.13
by James Henstridge
treat comments,etc as UTF-8. fix up option parsing in import script |
3 |
|
4 |
"""Bugzilla to Launchpad import logic"""
|
|
5 |
||
2701.1.2
by James Henstridge
bugzilla import code |
6 |
|
7 |
# Bugzilla schema:
|
|
4664.1.1
by Curtis Hovey
Normalized comments for bug 3732. |
8 |
# http://lxr.mozilla.org/mozilla/source/webtools/bugzilla/Bugzilla/DB/Schema.pm
|
2701.1.2
by James Henstridge
bugzilla import code |
9 |
|
4664.1.1
by Curtis Hovey
Normalized comments for bug 3732. |
10 |
# XXX: jamesh 2005-10-18
|
2701.1.6
by James Henstridge
add some TODO notes to bugzilla.py |
11 |
# Currently unhandled bug info:
|
12 |
# * Operating system and platform
|
|
13 |
# * version (not really used in Ubuntu bugzilla though)
|
|
14 |
# * keywords
|
|
15 |
# * private bugs (none of the canonical-only bugs seem sensitive though)
|
|
16 |
# * bug dependencies
|
|
2701.1.46
by James Henstridge
Switch to new Launchpad method naming style |
17 |
# * "bug XYZ" references inside comment text (at the moment we just
|
18 |
# insert the full URL to the bug afterwards).
|
|
2701.1.6
by James Henstridge
add some TODO notes to bugzilla.py |
19 |
#
|
20 |
# Not all of these are necessary though
|
|
21 |
||
2701.1.2
by James Henstridge
bugzilla import code |
22 |
__metaclass__ = type |
23 |
||
2701.1.4
by James Henstridge
migrate bug attachments too |
24 |
from cStringIO import StringIO |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
25 |
import datetime |
26 |
import logging |
|
2701.1.14
by James Henstridge
add URLs to bug references in comments, other bug fixes |
27 |
import re |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
28 |
|
2701.1.2
by James Henstridge
bugzilla import code |
29 |
import pytz |
5821.6.11
by James Henstridge
Fix doc/bugzilla-import.txt test. |
30 |
from storm.store import Store |
2701.1.2
by James Henstridge
bugzilla import code |
31 |
from zope.component import getUtility |
5821.6.11
by James Henstridge
Fix doc/bugzilla-import.txt test. |
32 |
|
8523.3.1
by Gavin Panella
Bugs tree reorg after automated migration. |
33 |
from canonical.launchpad.interfaces.emailaddress import IEmailAddressSet |
34 |
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
|
35 |
from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
|
12929.9.2
by j.c.sackett
Moved messages from canonical to lp.services |
36 |
from lp.services.messages.interfaces.message import IMessageSet |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
37 |
from canonical.launchpad.webapp import canonical_url |
11270.1.3
by Tim Penhey
Changed NotFoundError imports - gee there were a lot of them. |
38 |
from lp.app.errors import NotFoundError |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
39 |
from lp.bugs.interfaces.bug import ( |
40 |
CreateBugParams, |
|
41 |
IBugSet, |
|
42 |
)
|
|
10788.5.4
by Abel Deuring
added the bug_importer celebrity to the persons which can set any bug task assignee; fixed some test failures |
43 |
from lp.bugs.interfaces.bugattachment import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
44 |
BugAttachmentType, |
45 |
IBugAttachmentSet, |
|
46 |
)
|
|
8523.3.1
by Gavin Panella
Bugs tree reorg after automated migration. |
47 |
from lp.bugs.interfaces.bugtask import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
48 |
BugTaskImportance, |
49 |
BugTaskStatus, |
|
50 |
IBugTaskSet, |
|
51 |
)
|
|
8523.3.1
by Gavin Panella
Bugs tree reorg after automated migration. |
52 |
from lp.bugs.interfaces.bugwatch import IBugWatchSet |
53 |
from lp.bugs.interfaces.cve import ICveSet |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
54 |
from lp.registry.interfaces.person import ( |
55 |
IPersonSet, |
|
56 |
PersonCreationRationale, |
|
57 |
)
|
|
58 |
||
2701.1.2
by James Henstridge
bugzilla import code |
59 |
|
8523.3.1
by Gavin Panella
Bugs tree reorg after automated migration. |
60 |
logger = logging.getLogger('lp.bugs.scripts.bugzilla') |
2701.1.5
by James Henstridge
add some basic logging to the bugzilla module |
61 |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
62 |
def _add_tz(dt): |
2701.1.2
by James Henstridge
bugzilla import code |
63 |
"""Convert a naiive datetime value to a UTC datetime value."""
|
64 |
assert dt.tzinfo is None, 'add_tz() only accepts naiive datetime values' |
|
65 |
return datetime.datetime(dt.year, dt.month, dt.day, |
|
66 |
dt.hour, dt.minute, dt.second, |
|
67 |
dt.microsecond, tzinfo=pytz.timezone('UTC')) |
|
68 |
||
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
69 |
class BugzillaBackend: |
70 |
"""A wrapper for all the MySQL database access.
|
|
71 |
||
72 |
The main purpose of this is to make it possible to test the rest
|
|
73 |
of the import code without access to a MySQL database.
|
|
74 |
"""
|
|
2701.1.13
by James Henstridge
treat comments,etc as UTF-8. fix up option parsing in import script |
75 |
def __init__(self, conn, charset='UTF-8'): |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
76 |
self.conn = conn |
77 |
self.cursor = conn.cursor() |
|
2701.1.13
by James Henstridge
treat comments,etc as UTF-8. fix up option parsing in import script |
78 |
self.charset = charset |
79 |
||
80 |
def _decode(self, s): |
|
81 |
if s is not None: |
|
2701.1.50
by James Henstridge
Filter out non 16-bit unicode characters and surrogates from bugzilla data, |
82 |
value = s.decode(self.charset, 'replace') |
83 |
# Postgres doesn't like values outside of the basic multilingual
|
|
84 |
# plane (U+0000 - U+FFFF), so replace them (and surrogates) with
|
|
85 |
# U+FFFD (replacement character).
|
|
86 |
# Existance of these characters generally indicate an encoding
|
|
87 |
# problem in the existing Bugzilla data.
|
|
88 |
return re.sub(u'[^\u0000-\ud7ff\ue000-\uffff]', u'\ufffd', value) |
|
2701.1.13
by James Henstridge
treat comments,etc as UTF-8. fix up option parsing in import script |
89 |
else: |
90 |
return None |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
91 |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
92 |
def lookupUser(self, user_id): |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
93 |
"""Look up information about a particular Bugzilla user ID"""
|
94 |
self.cursor.execute('SELECT login_name, realname ' |
|
95 |
' FROM profiles '
|
|
96 |
' WHERE userid = %d' % user_id) |
|
97 |
if self.cursor.rowcount != 1: |
|
2701.1.47
by James Henstridge
Don't raise ValueError |
98 |
raise NotFoundError('could not look up user %d' % user_id) |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
99 |
(login_name, realname) = self.cursor.fetchone() |
2701.1.13
by James Henstridge
treat comments,etc as UTF-8. fix up option parsing in import script |
100 |
realname = self._decode(realname) |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
101 |
return (login_name, realname) |
102 |
||
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
103 |
def getBugInfo(self, bug_id): |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
104 |
"""Retrieve information about a bug."""
|
2701.1.2
by James Henstridge
bugzilla import code |
105 |
self.cursor.execute( |
106 |
'SELECT bug_id, assigned_to, bug_file_loc, bug_severity, '
|
|
107 |
' bug_status, creation_ts, short_desc, op_sys, priority, '
|
|
108 |
' products.name, rep_platform, reporter, version, '
|
|
109 |
' components.name, resolution, target_milestone, qa_contact, '
|
|
110 |
' status_whiteboard, keywords, alias '
|
|
111 |
' FROM bugs '
|
|
112 |
' INNER JOIN products ON bugs.product_id = products.id '
|
|
113 |
' INNER JOIN components ON bugs.component_id = components.id '
|
|
114 |
' WHERE bug_id = %d' % bug_id) |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
115 |
if self.cursor.rowcount != 1: |
2701.1.47
by James Henstridge
Don't raise ValueError |
116 |
raise NotFoundError('could not look up bug %d' % bug_id) |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
117 |
(bug_id, assigned_to, bug_file_loc, bug_severity, bug_status, |
118 |
creation_ts, short_desc, op_sys, priority, product, |
|
119 |
rep_platform, reporter, version, component, resolution, |
|
120 |
target_milestone, qa_contact, status_whiteboard, keywords, |
|
121 |
alias) = self.cursor.fetchone() |
|
122 |
||
2701.1.13
by James Henstridge
treat comments,etc as UTF-8. fix up option parsing in import script |
123 |
bug_file_loc = self._decode(bug_file_loc) |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
124 |
creation_ts = _add_tz(creation_ts) |
2701.1.13
by James Henstridge
treat comments,etc as UTF-8. fix up option parsing in import script |
125 |
product = self._decode(product) |
126 |
version = self._decode(version) |
|
127 |
component = self._decode(component) |
|
128 |
status_whiteboard = self._decode(status_whiteboard) |
|
129 |
keywords = self._decode(keywords) |
|
130 |
alias = self._decode(alias) |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
131 |
|
132 |
return (bug_id, assigned_to, bug_file_loc, bug_severity, |
|
133 |
bug_status, creation_ts, short_desc, op_sys, priority, |
|
134 |
product, rep_platform, reporter, version, component, |
|
135 |
resolution, target_milestone, qa_contact, |
|
136 |
status_whiteboard, keywords, alias) |
|
137 |
||
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
138 |
def getBugCcs(self, bug_id): |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
139 |
"""Get the IDs of the people CC'd to the bug."""
|
140 |
self.cursor.execute('SELECT who FROM cc WHERE bug_id = %d' |
|
141 |
% bug_id) |
|
142 |
return [row[0] for row in self.cursor.fetchall()] |
|
143 |
||
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
144 |
def getBugComments(self, bug_id): |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
145 |
"""Get the comments for the bug."""
|
146 |
self.cursor.execute('SELECT who, bug_when, thetext ' |
|
147 |
' FROM longdescs '
|
|
148 |
' WHERE bug_id = %d ' |
|
149 |
' ORDER BY bug_when' % bug_id) |
|
4664.1.1
by Curtis Hovey
Normalized comments for bug 3732. |
150 |
# XXX: jamesh 2005-12-07:
|
2701.1.45
by James Henstridge
Add a fix for the debzilla craziness in Ubuntu Bugzilla bug 248 |
151 |
# Due to a bug in Debzilla, Ubuntu bugzilla bug 248 has > 7800
|
152 |
# duplicate comments,consisting of someone's signature.
|
|
153 |
# For the import, just ignore those comments.
|
|
2701.1.13
by James Henstridge
treat comments,etc as UTF-8. fix up option parsing in import script |
154 |
return [(who, _add_tz(when), self._decode(thetext)) |
2701.1.45
by James Henstridge
Add a fix for the debzilla craziness in Ubuntu Bugzilla bug 248 |
155 |
for (who, when, thetext) in self.cursor.fetchall() |
156 |
if thetext != '\n--=20\n Jacobo Tarr=EDo | ' |
|
157 |
'http://jacobo.tarrio.org/\n\n\n'] |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
158 |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
159 |
def getBugAttachments(self, bug_id): |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
160 |
"""Get the attachments for the bug."""
|
161 |
self.cursor.execute('SELECT attach_id, creation_ts, description, ' |
|
162 |
' mimetype, ispatch, filename, thedata, '
|
|
163 |
' submitter_id '
|
|
164 |
' FROM attachments '
|
|
165 |
' WHERE bug_id = %d ' |
|
166 |
' ORDER BY attach_id' % bug_id) |
|
2701.1.13
by James Henstridge
treat comments,etc as UTF-8. fix up option parsing in import script |
167 |
return [(attach_id, _add_tz(creation_ts), |
168 |
self._decode(description), mimetype, |
|
169 |
ispatch, self._decode(filename), thedata, submitter_id) |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
170 |
for (attach_id, creation_ts, description, |
171 |
mimetype, ispatch, filename, thedata, |
|
172 |
submitter_id) in self.cursor.fetchall()] |
|
173 |
||
4589.2.1
by Elliot Murphy
Code cleanup, getting rid of places which have default args set to [] |
174 |
def findBugs(self, product=None, component=None, status=None): |
2701.1.31
by James Henstridge
Add code to process the bugzilla duplicates table, and import the data into |
175 |
"""Returns the requested bug IDs as a list"""
|
4589.2.1
by Elliot Murphy
Code cleanup, getting rid of places which have default args set to [] |
176 |
if product is None: |
177 |
product = [] |
|
178 |
if component is None: |
|
179 |
component = [] |
|
180 |
if status is None: |
|
181 |
status = [] |
|
2701.1.10
by James Henstridge
add findBugs code, and update to work with Mark's changes |
182 |
joins = [] |
183 |
conditions = [] |
|
184 |
if product: |
|
6602.5.5
by Tom Berger
fix some lint errors |
185 |
joins.append( |
186 |
'INNER JOIN products ON bugs.product_id = products.id') |
|
2701.1.10
by James Henstridge
add findBugs code, and update to work with Mark's changes |
187 |
conditions.append('products.name IN (%s)' % |
188 |
', '.join([self.conn.escape(p) for p in product])) |
|
189 |
if component: |
|
6602.5.5
by Tom Berger
fix some lint errors |
190 |
joins.append( |
191 |
'INNER JOIN components ON bugs.component_id = components.id') |
|
2701.1.10
by James Henstridge
add findBugs code, and update to work with Mark's changes |
192 |
conditions.append('components.name IN (%s)' % |
193 |
', '.join([self.conn.escape(c) for c in component])) |
|
194 |
if status: |
|
195 |
conditions.append('bugs.bug_status IN (%s)' % |
|
196 |
', '.join([self.conn.escape(s) for s in status])) |
|
197 |
if conditions: |
|
198 |
conditions = 'WHERE %s' % ' AND '.join(conditions) |
|
199 |
else: |
|
200 |
conditions = '' |
|
201 |
self.cursor.execute('SELECT bug_id FROM bugs %s %s ORDER BY bug_id' % |
|
202 |
(' '.join(joins), conditions)) |
|
203 |
return [bug_id for (bug_id,) in self.cursor.fetchall()] |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
204 |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
205 |
def getDuplicates(self): |
2701.1.31
by James Henstridge
Add code to process the bugzilla duplicates table, and import the data into |
206 |
"""Returns a list of (dupe_of, dupe) relations."""
|
207 |
self.cursor.execute('SELECT dupe_of, dupe FROM duplicates ' |
|
208 |
'ORDER BY dupe, dupe_of') |
|
209 |
return [(dupe_of, dupe) for (dupe_of, dupe) in self.cursor.fetchall()] |
|
210 |
||
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
211 |
class Bug: |
212 |
"""Representation of a Bugzilla Bug"""
|
|
213 |
def __init__(self, backend, bug_id): |
|
214 |
self.backend = backend |
|
2701.1.2
by James Henstridge
bugzilla import code |
215 |
(self.bug_id, self.assigned_to, self.bug_file_loc, self.bug_severity, |
216 |
self.bug_status, self.creation_ts, self.short_desc, self.op_sys, |
|
217 |
self.priority, self.product, self.rep_platform, self.reporter, |
|
218 |
self.version, self.component, self.resolution, |
|
219 |
self.target_milestone, self.qa_contact, self.status_whiteboard, |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
220 |
self.keywords, self.alias) = backend.getBugInfo(bug_id) |
2701.1.2
by James Henstridge
bugzilla import code |
221 |
|
222 |
self._ccs = None |
|
223 |
self._comments = None |
|
2701.1.4
by James Henstridge
migrate bug attachments too |
224 |
self._attachments = None |
2701.1.2
by James Henstridge
bugzilla import code |
225 |
|
226 |
@property
|
|
227 |
def ccs(self): |
|
228 |
"""Return the IDs of people CC'd to this bug"""
|
|
10788.5.4
by Abel Deuring
added the bug_importer celebrity to the persons which can set any bug task assignee; fixed some test failures |
229 |
if self._ccs is not None: |
230 |
return self._ccs |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
231 |
self._ccs = self.backend.getBugCcs(self.bug_id) |
2701.1.2
by James Henstridge
bugzilla import code |
232 |
return self._ccs |
233 |
||
234 |
@property
|
|
235 |
def comments(self): |
|
236 |
"""Return the comments attached to this bug"""
|
|
10788.5.4
by Abel Deuring
added the bug_importer celebrity to the persons which can set any bug task assignee; fixed some test failures |
237 |
if self._comments is not None: |
238 |
return self._comments |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
239 |
self._comments = self.backend.getBugComments(self.bug_id) |
2701.1.2
by James Henstridge
bugzilla import code |
240 |
return self._comments |
241 |
||
2701.1.4
by James Henstridge
migrate bug attachments too |
242 |
@property
|
243 |
def attachments(self): |
|
244 |
"""Return the attachments for this bug"""
|
|
10788.5.4
by Abel Deuring
added the bug_importer celebrity to the persons which can set any bug task assignee; fixed some test failures |
245 |
if self._attachments is not None: |
246 |
return self._attachments |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
247 |
self._attachments = self.backend.getBugAttachments(self.bug_id) |
2701.1.4
by James Henstridge
migrate bug attachments too |
248 |
return self._attachments |
249 |
||
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
250 |
def mapSeverity(self, bugtask): |
10788.5.4
by Abel Deuring
added the bug_importer celebrity to the persons which can set any bug task assignee; fixed some test failures |
251 |
"""Set a Launchpad bug task's importance based on this bug's severity.
|
252 |
"""
|
|
6602.5.3
by Tom Berger
convert all assingments to bugtask.importance to calls to transitionToImportance |
253 |
bug_importer = getUtility(ILaunchpadCelebrities).bug_importer |
254 |
importance_map = { |
|
3270.3.10
by Matthew Paul Thomas
Changes BugTaskSeverity to BugTaskImportance. |
255 |
'blocker': BugTaskImportance.CRITICAL, |
256 |
'critical': BugTaskImportance.CRITICAL, |
|
3270.3.47
by Matthew Paul Thomas
Changes Minor to Low, Major to High, and restores the Priority column to the database. |
257 |
'major': BugTaskImportance.HIGH, |
3270.3.11
by Matthew Paul Thomas
Makes bug pages and bug listings work. |
258 |
'normal': BugTaskImportance.MEDIUM, |
3270.3.47
by Matthew Paul Thomas
Changes Minor to Low, Major to High, and restores the Priority column to the database. |
259 |
'minor': BugTaskImportance.LOW, |
260 |
'trivial': BugTaskImportance.LOW, |
|
3270.3.10
by Matthew Paul Thomas
Changes BugTaskSeverity to BugTaskImportance. |
261 |
'enhancement': BugTaskImportance.WISHLIST |
6602.5.3
by Tom Berger
convert all assingments to bugtask.importance to calls to transitionToImportance |
262 |
}
|
263 |
importance = importance_map.get( |
|
264 |
self.bug_severity, BugTaskImportance.UNKNOWN) |
|
265 |
bugtask.transitionToImportance(importance, bug_importer) |
|
2701.1.2
by James Henstridge
bugzilla import code |
266 |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
267 |
def mapStatus(self, bugtask): |
2701.1.35
by James Henstridge
Address some of BjornT's review concerns |
268 |
"""Set a Launchpad bug task's status based on this bug's status.
|
269 |
||
270 |
If the bug is in the RESOLVED, VERIFIED or CLOSED states, the
|
|
271 |
bug resolution is also taken into account when mapping the
|
|
272 |
status.
|
|
273 |
||
274 |
Additional information about the bugzilla status is appended
|
|
275 |
to the bug task's status explanation.
|
|
276 |
"""
|
|
4318.3.10
by Gavin Panella
Changing transitionToStatus to accept user argument. |
277 |
bug_importer = getUtility(ILaunchpadCelebrities).bug_importer |
4785.3.7
by Jeroen Vermeulen
Removed whitespace at ends of lines |
278 |
|
2701.1.2
by James Henstridge
bugzilla import code |
279 |
if self.bug_status == 'ASSIGNED': |
4318.3.10
by Gavin Panella
Changing transitionToStatus to accept user argument. |
280 |
bugtask.transitionToStatus( |
281 |
BugTaskStatus.CONFIRMED, bug_importer) |
|
2701.1.21
by James Henstridge
Import NEEDINFO bugs as NEEDINFO rather than NEW |
282 |
elif self.bug_status == 'NEEDINFO': |
4318.3.10
by Gavin Panella
Changing transitionToStatus to accept user argument. |
283 |
bugtask.transitionToStatus( |
284 |
BugTaskStatus.INCOMPLETE, bug_importer) |
|
2701.1.2
by James Henstridge
bugzilla import code |
285 |
elif self.bug_status == 'PENDINGUPLOAD': |
4318.3.10
by Gavin Panella
Changing transitionToStatus to accept user argument. |
286 |
bugtask.transitionToStatus( |
287 |
BugTaskStatus.FIXCOMMITTED, bug_importer) |
|
2701.1.2
by James Henstridge
bugzilla import code |
288 |
elif self.bug_status in ['RESOLVED', 'VERIFIED', 'CLOSED']: |
289 |
# depends on the resolution:
|
|
290 |
if self.resolution == 'FIXED': |
|
4318.3.10
by Gavin Panella
Changing transitionToStatus to accept user argument. |
291 |
bugtask.transitionToStatus( |
292 |
BugTaskStatus.FIXRELEASED, bug_importer) |
|
2701.1.2
by James Henstridge
bugzilla import code |
293 |
else: |
4318.3.10
by Gavin Panella
Changing transitionToStatus to accept user argument. |
294 |
bugtask.transitionToStatus( |
295 |
BugTaskStatus.INVALID, bug_importer) |
|
2701.1.2
by James Henstridge
bugzilla import code |
296 |
else: |
4318.3.10
by Gavin Panella
Changing transitionToStatus to accept user argument. |
297 |
bugtask.transitionToStatus( |
298 |
BugTaskStatus.NEW, bug_importer) |
|
2701.1.2
by James Henstridge
bugzilla import code |
299 |
|
300 |
# add the status to the notes section, to account for any lost
|
|
301 |
# information
|
|
2701.1.5
by James Henstridge
add some basic logging to the bugzilla module |
302 |
bugzilla_status = 'Bugzilla status=%s' % self.bug_status |
303 |
if self.resolution: |
|
304 |
bugzilla_status += ' %s' % self.resolution |
|
305 |
bugzilla_status += ', product=%s' % self.product |
|
306 |
bugzilla_status += ', component=%s' % self.component |
|
307 |
||
2701.1.2
by James Henstridge
bugzilla import code |
308 |
if bugtask.statusexplanation: |
10788.5.4
by Abel Deuring
added the bug_importer celebrity to the persons which can set any bug task assignee; fixed some test failures |
309 |
bugtask.statusexplanation = '%s (%s)' % ( |
310 |
bugtask.statusexplanation, bugzilla_status) |
|
2701.1.2
by James Henstridge
bugzilla import code |
311 |
else: |
312 |
bugtask.statusexplanation = bugzilla_status |
|
313 |
||
314 |
||
315 |
class Bugzilla: |
|
316 |
"""Representation of a bugzilla instance"""
|
|
317 |
||
318 |
def __init__(self, conn): |
|
2701.1.8
by James Henstridge
add a doctest for the bugzilla import code |
319 |
if conn is not None: |
320 |
self.backend = BugzillaBackend(conn) |
|
321 |
else: |
|
322 |
self.backend = None |
|
2701.1.27
by James Henstridge
Create a debian bug watch and task if the Bugzilla bug's alias matches |
323 |
self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
324 |
self.debian = getUtility(ILaunchpadCelebrities).debian |
|
2701.1.29
by James Henstridge
ubuntu-bugzilla => celebrity |
325 |
self.bugtracker = getUtility(ILaunchpadCelebrities).ubuntu_bugzilla |
2701.1.27
by James Henstridge
Create a debian bug watch and task if the Bugzilla bug's alias matches |
326 |
self.debbugs = getUtility(ILaunchpadCelebrities).debbugs |
2701.1.2
by James Henstridge
bugzilla import code |
327 |
self.bugset = getUtility(IBugSet) |
2701.1.23
by James Henstridge
Add preliminary support for bugs in the UPSTREAM state, by creating a |
328 |
self.bugtaskset = getUtility(IBugTaskSet) |
329 |
self.bugwatchset = getUtility(IBugWatchSet) |
|
2701.1.2
by James Henstridge
bugzilla import code |
330 |
self.cveset = getUtility(ICveSet) |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
331 |
self.personset = getUtility(IPersonSet) |
2701.1.33
by James Henstridge
If the person has no preferred email address, set the email from the |
332 |
self.emailset = getUtility(IEmailAddressSet) |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
333 |
self.person_mapping = {} |
334 |
||
335 |
def person(self, bugzilla_id): |
|
336 |
"""Get the Launchpad person corresponding to the given Bugzilla ID"""
|
|
2701.1.35
by James Henstridge
Address some of BjornT's review concerns |
337 |
# Bugzilla treats a user ID of 0 as a NULL
|
338 |
if bugzilla_id == 0: |
|
339 |
return None |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
340 |
|
341 |
# Try and get the person using a cache of the mapping. We
|
|
342 |
# check to make sure the person still exists and has not been
|
|
343 |
# merged.
|
|
2701.1.8
by James Henstridge
add a doctest for the bugzilla import code |
344 |
person = None |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
345 |
launchpad_id = self.person_mapping.get(bugzilla_id) |
346 |
if launchpad_id is not None: |
|
2908.4.3
by Guilherme Salgado
some fixes as per Rob's review |
347 |
person = self.personset.get(launchpad_id) |
348 |
if person is not None and person.merged is not None: |
|
349 |
person = None |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
350 |
|
351 |
# look up the person
|
|
352 |
if person is None: |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
353 |
email, displayname = self.backend.lookupUser(bugzilla_id) |
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
354 |
|
355 |
person = self.personset.ensurePerson( |
|
3691.164.3
by Guilherme Salgado
Update all places where a person can be created to provide creation_rationale/creation_comment and change the registration process to Just Work when someone tries to register with an email address that is associated with an unvalidated account |
356 |
email, displayname, PersonCreationRationale.BUGIMPORT, |
3691.164.16
by Guilherme Salgado
Lots of fixes and tests suggested by Bjorn |
357 |
comment=('when importing bugs from %s' |
3691.164.3
by Guilherme Salgado
Update all places where a person can be created to provide creation_rationale/creation_comment and change the registration process to Just Work when someone tries to register with an email address that is associated with an unvalidated account |
358 |
% self.bugtracker.baseurl)) |
2701.1.33
by James Henstridge
If the person has no preferred email address, set the email from the |
359 |
|
360 |
# Bugzilla performs similar address checks to Launchpad, so
|
|
361 |
# if the Launchpad account has no preferred email, use the
|
|
362 |
# Bugzilla one.
|
|
363 |
emailaddr = self.emailset.getByEmail(email) |
|
364 |
assert emailaddr is not None |
|
2701.1.35
by James Henstridge
Address some of BjornT's review concerns |
365 |
if person.preferredemail != emailaddr: |
366 |
person.validateAndEnsurePreferredEmail(emailaddr) |
|
4785.3.7
by Jeroen Vermeulen
Removed whitespace at ends of lines |
367 |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
368 |
self.person_mapping[bugzilla_id] = person.id |
369 |
||
370 |
return person |
|
2701.1.2
by James Henstridge
bugzilla import code |
371 |
|
2950.1.1
by James Henstridge
add a custom source package mapping of the "linux" component |
372 |
def _getPackageNames(self, bug): |
373 |
"""Returns the source and binary package names for the given bug."""
|
|
374 |
# we currently only support mapping Ubuntu bugs ...
|
|
375 |
if bug.product != 'Ubuntu': |
|
376 |
raise AssertionError('product must be Ubuntu') |
|
377 |
||
378 |
# kernel bugs are currently filed against the "linux"
|
|
379 |
# component, which is not a source or binary package. The
|
|
380 |
# following mapping was provided by BenC:
|
|
381 |
if bug.component == 'linux': |
|
382 |
cutoffdate = datetime.datetime(2004, 12, 1, |
|
383 |
tzinfo=pytz.timezone('UTC')) |
|
384 |
if bug.bug_status == 'NEEDINFO' and bug.creation_ts < cutoffdate: |
|
385 |
pkgname = 'linux-source-2.6.12' |
|
386 |
else: |
|
387 |
pkgname = 'linux-source-2.6.15' |
|
388 |
else: |
|
389 |
pkgname = bug.component.encode('ASCII') |
|
4785.3.7
by Jeroen Vermeulen
Removed whitespace at ends of lines |
390 |
|
2950.1.1
by James Henstridge
add a custom source package mapping of the "linux" component |
391 |
try: |
3504.1.58
by kiko
Hack Distribution.getPackageNames around a bit: rename it to guessPackageNames to make clear it is a best-effort and might not give the right answer; test the fact that it restricts publication to a single distribution, and ensure that the code does that; revamp comments. |
392 |
srcpkg, binpkg = self.ubuntu.guessPackageNames(pkgname) |
2950.1.1
by James Henstridge
add a custom source package mapping of the "linux" component |
393 |
except NotFoundError, e: |
394 |
logger.warning('could not find package name for "%s": %s', |
|
395 |
pkgname, str(e)) |
|
396 |
srcpkg = binpkg = None |
|
397 |
||
398 |
return srcpkg, binpkg |
|
399 |
||
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
400 |
def getLaunchpadBugTarget(self, bug): |
2701.1.2
by James Henstridge
bugzilla import code |
401 |
"""Returns a dictionary of arguments to createBug() that correspond
|
402 |
to the given bugzilla bug.
|
|
403 |
"""
|
|
2950.1.1
by James Henstridge
add a custom source package mapping of the "linux" component |
404 |
srcpkg, binpkg = self._getPackageNames(bug) |
2701.1.2
by James Henstridge
bugzilla import code |
405 |
return { |
2701.1.27
by James Henstridge
Create a debian bug watch and task if the Bugzilla bug's alias matches |
406 |
'distribution': self.ubuntu, |
2701.1.2
by James Henstridge
bugzilla import code |
407 |
'sourcepackagename': srcpkg, |
408 |
}
|
|
409 |
||
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
410 |
def getLaunchpadMilestone(self, bug): |
2701.1.43
by James Henstridge
Migrate Bugzilla milestones to Launchpad equivalents |
411 |
"""Return the Launchpad milestone for a Bugzilla bug.
|
412 |
||
413 |
If the milestone does not exist, then it is created.
|
|
414 |
"""
|
|
415 |
if bug.product != 'Ubuntu': |
|
2701.1.47
by James Henstridge
Don't raise ValueError |
416 |
raise AssertionError('product must be Ubuntu') |
2701.1.43
by James Henstridge
Migrate Bugzilla milestones to Launchpad equivalents |
417 |
|
2701.1.47
by James Henstridge
Don't raise ValueError |
418 |
# Bugzilla uses a value of "---" to represent "no selected Milestone"
|
419 |
# Launchpad represents this by setting the milestone column to NULL.
|
|
2701.1.43
by James Henstridge
Migrate Bugzilla milestones to Launchpad equivalents |
420 |
if bug.target_milestone is None or bug.target_milestone == '---': |
421 |
return None |
|
422 |
||
423 |
# generate a Launchpad name from the Milestone name:
|
|
424 |
name = re.sub(r'[^a-z0-9\+\.\-]', '-', bug.target_milestone.lower()) |
|
425 |
||
3472.2.2
by Mark Shuttleworth
Test fixes for milestones. |
426 |
milestone = self.ubuntu.getMilestone(name) |
5821.6.11
by James Henstridge
Fix doc/bugzilla-import.txt test. |
427 |
if milestone is None: |
428 |
milestone = self.ubuntu.currentseries.newMilestone(name) |
|
429 |
Store.of(milestone).flush() |
|
430 |
return milestone |
|
2701.1.43
by James Henstridge
Migrate Bugzilla milestones to Launchpad equivalents |
431 |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
432 |
def getLaunchpadUpstreamProduct(self, bug): |
2701.1.43
by James Henstridge
Migrate Bugzilla milestones to Launchpad equivalents |
433 |
"""Find the upstream product for the given Bugzilla bug.
|
434 |
||
435 |
This function relies on the package -> product linkage having been
|
|
436 |
entered in advance.
|
|
437 |
"""
|
|
2950.1.1
by James Henstridge
add a custom source package mapping of the "linux" component |
438 |
srcpkgname, binpkgname = self._getPackageNames(bug) |
2701.1.23
by James Henstridge
Add preliminary support for bugs in the UPSTREAM state, by creating a |
439 |
# find a product series
|
440 |
series = None |
|
9760.8.1
by Brad Crittenden
Change the non-English 'serieses' to 'series' throughout our codebase. |
441 |
for series in self.ubuntu.series: |
4285.2.4
by Mark Shuttleworth
Test fixes with renamed distrorelease |
442 |
srcpkg = series.getSourcePackage(srcpkgname) |
2701.1.23
by James Henstridge
Add preliminary support for bugs in the UPSTREAM state, by creating a |
443 |
if srcpkg: |
444 |
series = srcpkg.productseries |
|
445 |
if series: |
|
446 |
return series.product |
|
447 |
else: |
|
2701.1.26
by James Henstridge
Warn if the upstream package could not be deduced when linking to |
448 |
logger.warning('could not find upstream product for ' |
449 |
'source package "%s"', srcpkgname.name) |
|
2701.1.23
by James Henstridge
Add preliminary support for bugs in the UPSTREAM state, by creating a |
450 |
return None |
4664.1.1
by Curtis Hovey
Normalized comments for bug 3732. |
451 |
|
2701.1.14
by James Henstridge
add URLs to bug references in comments, other bug fixes |
452 |
_bug_re = re.compile('bug\s*#?\s*(?P<id>\d+)', re.IGNORECASE) |
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
453 |
def replaceBugRef(self, match): |
4664.1.1
by Curtis Hovey
Normalized comments for bug 3732. |
454 |
# XXX: jamesh 2005-10-24:
|
2701.1.14
by James Henstridge
add URLs to bug references in comments, other bug fixes |
455 |
# this is where bug number rewriting would be plugged in
|
456 |
bug_id = int(match.group('id')) |
|
2950.2.2
by James Henstridge
When rewriting bug references from imported bugzilla comments, include a |
457 |
url = '%s/%d' % (canonical_url(self.bugtracker), bug_id) |
2701.1.14
by James Henstridge
add URLs to bug references in comments, other bug fixes |
458 |
return '%s [%s]' % (match.group(0), url) |
459 |
||
460 |
||
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
461 |
def handleBug(self, bug_id): |
2701.1.10
by James Henstridge
add findBugs code, and update to work with Mark's changes |
462 |
"""Maybe import a single bug.
|
463 |
||
464 |
If the bug has already been imported (detected by checking for
|
|
465 |
a bug watch), it is skipped.
|
|
466 |
"""
|
|
2701.1.5
by James Henstridge
add some basic logging to the bugzilla module |
467 |
logger.info('Handling Bugzilla bug %d', bug_id) |
4785.3.7
by Jeroen Vermeulen
Removed whitespace at ends of lines |
468 |
|
2701.1.2
by James Henstridge
bugzilla import code |
469 |
# is there a bug watch on the bug?
|
2701.1.5
by James Henstridge
add some basic logging to the bugzilla module |
470 |
lp_bug = self.bugset.queryByRemoteBug(self.bugtracker, bug_id) |
2701.1.2
by James Henstridge
bugzilla import code |
471 |
|
472 |
# if we already have an associated bug, don't add a new one.
|
|
473 |
if lp_bug is not None: |
|
2701.1.5
by James Henstridge
add some basic logging to the bugzilla module |
474 |
logger.info('Bugzilla bug %d is already being watched by ' |
475 |
'Launchpad bug %d', bug_id, lp_bug.id) |
|
476 |
return lp_bug |
|
477 |
||
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
478 |
bug = Bug(self.backend, bug_id) |
2701.1.2
by James Henstridge
bugzilla import code |
479 |
|
480 |
comments = bug.comments[:] |
|
481 |
||
482 |
# create a message for the initial comment:
|
|
483 |
msgset = getUtility(IMessageSet) |
|
484 |
who, when, text = comments.pop(0) |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
485 |
text = self._bug_re.sub(self.replaceBugRef, text) |
4697.2.4
by Matthew Paul Thomas
Fixes from self-review. |
486 |
# If a URL is associated with the bug, add it to the description:
|
4697.2.1
by Matthew Paul Thomas
Fixes bug 5998 ('Add URL' link only visible if bug already has URLs associated with it), by removing the idea of Web links for bugs. |
487 |
if bug.bug_file_loc: |
4697.2.2
by Matthew Paul Thomas
Fixes doctests. |
488 |
text = text + '\n\n' + bug.bug_file_loc |
489 |
# the initial comment can't be empty:
|
|
2701.1.14
by James Henstridge
add URLs to bug references in comments, other bug fixes |
490 |
if not text.strip(): |
491 |
text = '<empty comment>' |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
492 |
msg = msgset.fromText(bug.short_desc, text, self.person(who), when) |
2701.1.2
by James Henstridge
bugzilla import code |
493 |
|
494 |
# create the bug
|
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
495 |
target = self.getLaunchpadBugTarget(bug) |
3598.1.8
by Brad Bollenbach
refactor IBugSet.createBug to use a parameter object |
496 |
params = CreateBugParams( |
497 |
msg=msg, datecreated=bug.creation_ts, title=bug.short_desc, |
|
498 |
owner=self.person(bug.reporter)) |
|
499 |
params.setBugTarget(**target) |
|
500 |
lp_bug = self.bugset.createBug(params) |
|
2701.1.2
by James Henstridge
bugzilla import code |
501 |
|
502 |
# add the bug watch:
|
|
6128.2.17
by Stuart Bishop
PG8.3 fixes |
503 |
lp_bug.addWatch(self.bugtracker, str(bug.bug_id), lp_bug.owner) |
2701.1.2
by James Henstridge
bugzilla import code |
504 |
|
505 |
# add remaining comments, and add CVEs found in all text
|
|
4476.1.3
by Bjorn Tillenius
fix test to expose problem when creating CVEs on package uploads. Fix the test failure by requiring a user attribute for linkCVE and findCvesInText. |
506 |
lp_bug.findCvesInText(text, lp_bug.owner) |
2701.1.2
by James Henstridge
bugzilla import code |
507 |
for (who, when, text) in comments: |
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
508 |
text = self._bug_re.sub(self.replaceBugRef, text) |
2701.1.14
by James Henstridge
add URLs to bug references in comments, other bug fixes |
509 |
msg = msgset.fromText(msg.followup_title, text, |
510 |
self.person(who), when) |
|
511 |
lp_bug.linkMessage(msg) |
|
2701.1.2
by James Henstridge
bugzilla import code |
512 |
|
513 |
# subscribe QA contact and CC's
|
|
514 |
if bug.qa_contact: |
|
5454.1.5
by Tom Berger
record who created each bug subscription, and display the result in the title of the subscriber link |
515 |
lp_bug.subscribe( |
516 |
self.person(bug.qa_contact), self.person(bug.reporter)) |
|
2701.1.2
by James Henstridge
bugzilla import code |
517 |
for cc in bug.ccs: |
5454.1.5
by Tom Berger
record who created each bug subscription, and display the result in the title of the subscriber link |
518 |
lp_bug.subscribe(self.person(cc), self.person(bug.reporter)) |
2701.1.2
by James Henstridge
bugzilla import code |
519 |
|
520 |
# translate bugzilla status and severity to LP equivalents
|
|
521 |
task = lp_bug.bugtasks[0] |
|
522 |
task.datecreated = bug.creation_ts |
|
3455.1.4
by Brad Bollenbach
checkpoint |
523 |
task.transitionToAssignee(self.person(bug.assigned_to)) |
2701.1.2
by James Henstridge
bugzilla import code |
524 |
task.statusexplanation = bug.status_whiteboard |
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
525 |
bug.mapSeverity(task) |
526 |
bug.mapStatus(task) |
|
2701.1.2
by James Henstridge
bugzilla import code |
527 |
|
2701.1.27
by James Henstridge
Create a debian bug watch and task if the Bugzilla bug's alias matches |
528 |
# bugs with an alias of the form "deb1234" have been imported
|
529 |
# from the Debian bug tracker by the "debzilla" program. For
|
|
530 |
# these bugs, generate a task and watch on the corresponding
|
|
531 |
# bugs.debian.org bug.
|
|
2976.1.1
by James Henstridge
Support migration of bug aliases |
532 |
if bug.alias: |
533 |
if re.match(r'^deb\d+$', bug.alias): |
|
534 |
watch = self.bugwatchset.createBugWatch( |
|
5821.6.11
by James Henstridge
Fix doc/bugzilla-import.txt test. |
535 |
lp_bug, lp_bug.owner, self.debbugs, bug.alias[3:]) |
2976.1.1
by James Henstridge
Support migration of bug aliases |
536 |
debtask = self.bugtaskset.createTask( |
537 |
lp_bug, |
|
538 |
owner=lp_bug.owner, |
|
539 |
distribution=self.debian, |
|
540 |
sourcepackagename=target['sourcepackagename']) |
|
541 |
debtask.datecreated = bug.creation_ts |
|
542 |
debtask.bugwatch = watch |
|
543 |
else: |
|
544 |
# generate a Launchpad name from the alias:
|
|
545 |
name = re.sub(r'[^a-z0-9\+\.\-]', '-', bug.alias.lower()) |
|
546 |
lp_bug.name = name |
|
2701.1.27
by James Henstridge
Create a debian bug watch and task if the Bugzilla bug's alias matches |
547 |
|
2701.1.23
by James Henstridge
Add preliminary support for bugs in the UPSTREAM state, by creating a |
548 |
# for UPSTREAM bugs, try to find whether the URL field contains
|
549 |
# a bug reference.
|
|
550 |
if bug.bug_status == 'UPSTREAM': |
|
551 |
# see if the URL field contains a bug tracker reference
|
|
552 |
watches = self.bugwatchset.fromText(bug.bug_file_loc, |
|
553 |
lp_bug, lp_bug.owner) |
|
554 |
# find the upstream product for this bug
|
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
555 |
product = self.getLaunchpadUpstreamProduct(bug) |
2701.1.23
by James Henstridge
Add preliminary support for bugs in the UPSTREAM state, by creating a |
556 |
|
557 |
# if we created a watch, and there is an upstream product,
|
|
558 |
# create a new task and link it to the watch.
|
|
2701.1.35
by James Henstridge
Address some of BjornT's review concerns |
559 |
if len(watches) > 0: |
560 |
if product: |
|
561 |
upstreamtask = self.bugtaskset.createTask( |
|
562 |
lp_bug, product=product, owner=lp_bug.owner) |
|
563 |
upstreamtask.datecreated = bug.creation_ts |
|
564 |
upstreamtask.bugwatch = watches[0] |
|
565 |
else: |
|
566 |
logger.warning('Could not find upstream product to link ' |
|
567 |
'bug %d to', lp_bug.id) |
|
2701.1.23
by James Henstridge
Add preliminary support for bugs in the UPSTREAM state, by creating a |
568 |
|
2701.1.35
by James Henstridge
Address some of BjornT's review concerns |
569 |
# translate milestone linkage
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
570 |
task.milestone = self.getLaunchpadMilestone(bug) |
2701.1.2
by James Henstridge
bugzilla import code |
571 |
|
2701.1.4
by James Henstridge
migrate bug attachments too |
572 |
# import attachments
|
573 |
for (attach_id, creation_ts, description, mimetype, ispatch, |
|
574 |
filename, thedata, submitter_id) in bug.attachments: |
|
2701.1.50
by James Henstridge
Filter out non 16-bit unicode characters and surrogates from bugzilla data, |
575 |
# if the filename is missing for some reason, use a generic one.
|
576 |
if filename is None or filename.strip() == '': |
|
577 |
filename = 'untitled' |
|
2701.1.5
by James Henstridge
add some basic logging to the bugzilla module |
578 |
logger.debug('Creating attachment %s for bug %d', |
579 |
filename, bug.bug_id) |
|
2701.1.4
by James Henstridge
migrate bug attachments too |
580 |
if ispatch: |
581 |
attach_type = BugAttachmentType.PATCH |
|
582 |
mimetype = 'text/plain' |
|
583 |
else: |
|
584 |
attach_type = BugAttachmentType.UNSPECIFIED |
|
585 |
||
2701.1.5
by James Henstridge
add some basic logging to the bugzilla module |
586 |
# look for a message starting with "Created an attachment (id=NN)"
|
2701.1.4
by James Henstridge
migrate bug attachments too |
587 |
for msg in lp_bug.messages: |
2938.3.3
by Bjorn Tillenius
remove IMessage.contents since there's no current use case for it. |
588 |
if msg.text_contents.startswith( |
589 |
'Created an attachment (id=%d)' % attach_id): |
|
2701.1.4
by James Henstridge
migrate bug attachments too |
590 |
break
|
591 |
else: |
|
592 |
# could not find the add message, so create one:
|
|
593 |
msg = msgset.fromText(description, |
|
594 |
'Created attachment %s' % filename, |
|
2701.1.7
by James Henstridge
extract actual mysql stuff into a separate class so it can be replaced |
595 |
self.person(submitter_id), |
2701.1.4
by James Henstridge
migrate bug attachments too |
596 |
creation_ts) |
597 |
lp_bug.linkMessage(msg) |
|
598 |
||
599 |
filealias = getUtility(ILibraryFileAliasSet).create( |
|
600 |
name=filename, |
|
601 |
size=len(thedata), |
|
602 |
file=StringIO(thedata), |
|
603 |
contentType=mimetype) |
|
604 |
||
605 |
getUtility(IBugAttachmentSet).create( |
|
606 |
bug=lp_bug, filealias=filealias, attach_type=attach_type, |
|
607 |
title=description, message=msg) |
|
2701.1.8
by James Henstridge
add a doctest for the bugzilla import code |
608 |
|
609 |
return lp_bug |
|
2701.1.10
by James Henstridge
add findBugs code, and update to work with Mark's changes |
610 |
|
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
611 |
def processDuplicates(self, trans): |
2701.1.35
by James Henstridge
Address some of BjornT's review concerns |
612 |
"""Mark Launchpad bugs as duplicates based on Bugzilla duplicates.
|
613 |
||
614 |
Launchpad bug A will be marked as a duplicate of bug B if:
|
|
615 |
* bug A watches bugzilla bug A'
|
|
616 |
* bug B watches bugzilla bug B'
|
|
617 |
* bug A' is a duplicate of bug B'
|
|
618 |
* bug A is not currently a duplicate of any other bug.
|
|
619 |
"""
|
|
2701.1.31
by James Henstridge
Add code to process the bugzilla duplicates table, and import the data into |
620 |
logger.info('Processing duplicate bugs') |
621 |
bugmap = {} |
|
622 |
def getlpbug(bugid): |
|
623 |
"""Get the Launchpad bug corresponding to the given remote ID
|
|
624 |
||
625 |
This function makes use of a cache dictionary to reduce the
|
|
626 |
number of lookups.
|
|
627 |
"""
|
|
628 |
lpbugid = bugmap.get(bugid) |
|
629 |
if lpbugid is not None: |
|
630 |
if lpbugid != 0: |
|
631 |
lpbug = self.bugset.get(lpbugid) |
|
632 |
else: |
|
633 |
lpbug = None |
|
634 |
else: |
|
635 |
lpbug = self.bugset.queryByRemoteBug(self.bugtracker, bugid) |
|
636 |
if lpbug is not None: |
|
637 |
bugmap[bugid] = lpbug.id |
|
638 |
else: |
|
639 |
bugmap[bugid] = 0 |
|
640 |
return lpbug |
|
641 |
||
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
642 |
for (dupe_of, dupe) in self.backend.getDuplicates(): |
2701.1.31
by James Henstridge
Add code to process the bugzilla duplicates table, and import the data into |
643 |
# get the Launchpad bugs corresponding to the two Bugzilla bugs:
|
2701.1.36
by James Henstridge
More changes based on BjornT's feedback |
644 |
trans.begin() |
645 |
lpdupe_of = getlpbug(dupe_of) |
|
646 |
lpdupe = getlpbug(dupe) |
|
647 |
# if both bugs exist in Launchpad, and lpdupe is not already
|
|
648 |
# a duplicate, mark it as a duplicate of lpdupe_of.
|
|
649 |
if (lpdupe_of is not None and lpdupe is not None and |
|
650 |
lpdupe.duplicateof is None): |
|
651 |
logger.info('Marking %d as a duplicate of %d', |
|
652 |
lpdupe.id, lpdupe_of.id) |
|
11272.1.1
by Deryck Hodge
First pass at getting my dupe finder work that was |
653 |
lpdupe.markAsDuplicate(lpdupe_of) |
2701.1.36
by James Henstridge
More changes based on BjornT's feedback |
654 |
trans.commit() |
655 |
||
4589.2.1
by Elliot Murphy
Code cleanup, getting rid of places which have default args set to [] |
656 |
def importBugs(self, trans, product=None, component=None, status=None): |
2701.1.36
by James Henstridge
More changes based on BjornT's feedback |
657 |
"""Import Bugzilla bugs matching the given constraints.
|
658 |
||
659 |
Each of product, component and status gives a list of
|
|
660 |
products, components or statuses to limit the import to. An
|
|
661 |
empty list matches all products, components or statuses.
|
|
662 |
"""
|
|
4589.2.1
by Elliot Murphy
Code cleanup, getting rid of places which have default args set to [] |
663 |
if product is None: |
664 |
product = [] |
|
665 |
if component is None: |
|
666 |
component = [] |
|
667 |
if status is None: |
|
668 |
status = [] |
|
669 |
||
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
670 |
bugs = self.backend.findBugs(product=product, |
671 |
component=component, |
|
672 |
status=status) |
|
2701.1.10
by James Henstridge
add findBugs code, and update to work with Mark's changes |
673 |
for bug_id in bugs: |
2701.1.36
by James Henstridge
More changes based on BjornT's feedback |
674 |
trans.begin() |
2701.1.10
by James Henstridge
add findBugs code, and update to work with Mark's changes |
675 |
try: |
2701.1.51
by James Henstridge
Revert method name changes in rev 2747 in light of decision at meeting. |
676 |
self.handleBug(bug_id) |
2701.1.14
by James Henstridge
add URLs to bug references in comments, other bug fixes |
677 |
except (SystemExit, KeyboardInterrupt): |
678 |
raise
|
|
2701.1.10
by James Henstridge
add findBugs code, and update to work with Mark's changes |
679 |
except: |
680 |
logger.exception('Could not import Bugzilla bug #%d', bug_id) |
|
681 |
trans.abort() |
|
682 |
else: |
|
683 |
trans.commit() |