Merge lp:~jelmer/launchpad/code-import-bug-link into lp:launchpad/db-devel
- code-import-bug-link
- Merge into db-devel
Proposed by
Jelmer Vernooij
Status: | Work in progress |
---|---|
Proposed branch: | lp:~jelmer/launchpad/code-import-bug-link |
Merge into: | lp:launchpad/db-devel |
Diff against target: |
2614 lines (+257/-2015) 24 files modified
cronscripts/create-debwatches.py (+0/-102) cronscripts/update-debwatches.py (+0/-240) database/schema/comments.sql (+1/-0) database/schema/patch-2208-00-2.sql (+16/-0) lib/canonical/launchpad/scripts/debsync.py (+0/-201) lib/lp/bugs/browser/bugtask.py (+18/-0) lib/lp/bugs/browser/configure.zcml (+5/-0) lib/lp/bugs/browser/tests/test_bugtask.py (+24/-0) lib/lp/bugs/doc/bugzilla-import.txt (+0/-562) lib/lp/bugs/scripts/bugzilla.py (+0/-687) lib/lp/bugs/templates/bugtarget-tasks-and-nominations-table-row.pt (+11/-0) lib/lp/bugs/tests/test_doc.py (+0/-6) lib/lp/code/configure.zcml (+3/-1) lib/lp/code/interfaces/codeimport.py (+14/-0) lib/lp/code/model/codeimport.py (+12/-0) lib/lp/code/model/tests/test_codeimport.py (+25/-0) lib/lp/codehosting/codeimport/tests/test_uifactory.py (+66/-12) lib/lp/codehosting/codeimport/uifactory.py (+51/-9) lib/lp/codehosting/codeimport/worker.py (+5/-4) lib/lp/registry/browser/distributionsourcepackage.py (+6/-0) lib/lp/registry/templates/distributionsourcepackage-index.pt (+0/-2) lib/lp/services/scripts/tests/__init__.py (+0/-1) scripts/bugzilla-import.py (+0/-97) scripts/migrate-bugzilla-initialcontacts.py (+0/-91) |
To merge this branch: | bzr merge lp:~jelmer/launchpad/code-import-bug-link |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stuart Bishop (community) | db | Approve | |
Robert Collins | db | Pending | |
Launchpad code reviewers | code | Pending | |
Review via email: mp+33595@code.launchpad.net |
Commit message
Add a field for linking code imports to related bug reports that explain their failing.
Description of the change
This adds a field to CodeImport that can be used to link the bug that causes the import to be failing. This field can only be set if the import is marked failing. If the field is set when the status of the import changes the code import is unlinked from the bug.
Pre-implementation call
=======
I haven't had a pre-implementation call.
Tests
=====
./bin/test lp.code.
To post a comment you must log in.
- 9677. By Jelmer Vernooij
-
merge db-devel
- 9678. By Jelmer Vernooij
-
merge trunk
Unmerged revisions
- 9678. By Jelmer Vernooij
-
merge trunk
- 9677. By Jelmer Vernooij
-
merge db-devel
- 9676. By Jelmer Vernooij
-
Add model code.
- 9675. By Jelmer Vernooij
-
Add database patch for bug URL column.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === removed file 'cronscripts/create-debwatches.py' |
2 | --- cronscripts/create-debwatches.py 2011-05-29 01:36:05 +0000 |
3 | +++ cronscripts/create-debwatches.py 1970-01-01 00:00:00 +0000 |
4 | @@ -1,102 +0,0 @@ |
5 | -#!/usr/bin/python -S |
6 | -# |
7 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
8 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
9 | - |
10 | -# pylint: disable-msg=C0103,W0403 |
11 | - |
12 | -# This script aims to ensure that there is a Malone watch on Debian bugs |
13 | -# that meet certain criteria. The Malone watch will be linked to a BugTask |
14 | -# on Debian for that bug. The business of syncing is handled separately. |
15 | - |
16 | -__metaclass__ = type |
17 | - |
18 | -import _pythonpath |
19 | -import os |
20 | -import logging |
21 | - |
22 | -# zope bits |
23 | -from zope.component import getUtility |
24 | - |
25 | -# canonical launchpad modules |
26 | -from lp.services.scripts.base import ( |
27 | - LaunchpadCronScript, LaunchpadScriptFailure) |
28 | -from canonical.launchpad.scripts.debsync import do_import |
29 | -from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
30 | - |
31 | - |
32 | -# setup core values and defaults |
33 | -debbugs_location_default = '/srv/bugs-mirror.debian.org/' |
34 | -debbugs_pl = '../lib/canonical/launchpad/scripts/debbugs-log.pl' |
35 | - |
36 | -# the minimum age, in days, of a debbugs bug before we will import it |
37 | -MIN_AGE = 7 |
38 | - |
39 | - |
40 | -class CreateDebWatches(LaunchpadCronScript): |
41 | - description = """ |
42 | - This script syncs debbugs from http://bugs.debian.org/ into Malone. |
43 | - It selects interesting bugs in debian and makes sure that there is a |
44 | - Malone bug for each of them. See debwatchsync for a tool that |
45 | - syncronises the bugs in Malone and debbugs, too. |
46 | - """ |
47 | - loglevel = logging.WARNING |
48 | - def add_my_options(self): |
49 | - self.parser.set_defaults(max=None, debbugs=debbugs_location_default) |
50 | - self.parser.add_option('--debbugs', action='store', type='string', |
51 | - dest='debbugs', |
52 | - help="The location of your debbugs database.") |
53 | - self.parser.add_option( |
54 | - '--max', action='store', type='int', dest='max', |
55 | - help="The maximum number of bugs to create.") |
56 | - self.parser.add_option('--package', action='append', type='string', |
57 | - help="A list of packages for which we should import bugs.", |
58 | - dest="packages", default=[]) |
59 | - |
60 | - def main(self): |
61 | - index_db_path = os.path.join(self.options.debbugs, 'index/index.db') |
62 | - if not os.path.exists(index_db_path): |
63 | - # make sure the debbugs location looks sane |
64 | - raise LaunchpadScriptFailure('%s is not a debbugs db.' |
65 | - % self.options.debbugs) |
66 | - |
67 | - # Make sure we import any Debian bugs specified on the command line |
68 | - target_bugs = set() |
69 | - for arg in self.args: |
70 | - try: |
71 | - target_bug = int(arg) |
72 | - except ValueError: |
73 | - self.logger.error( |
74 | - '%s is not a valid debian bug number.' % arg) |
75 | - target_bugs.add(target_bug) |
76 | - |
77 | - target_package_set = set() |
78 | - previousimportset = set() |
79 | - |
80 | - self.logger.info('Calculating target package set...') |
81 | - |
82 | - # first find all the published ubuntu packages |
83 | - ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
84 | - for p in ubuntu.currentrelease.getAllPublishedBinaries(): |
85 | - target_package_set.add( |
86 | - p.binarypackagerelease.binarypackagename.name) |
87 | - # then add packages passed on the command line |
88 | - for package in self.options.packages: |
89 | - target_package_set.add(package) |
90 | - self.logger.info( |
91 | - '%d binary packages targeted.' % len(target_package_set)) |
92 | - |
93 | - self.txn.abort() |
94 | - self.txn.begin() |
95 | - do_import(self.logger, self.options.max, self.options.debbugs, |
96 | - target_bugs, target_package_set, previousimportset, MIN_AGE, |
97 | - debbugs_pl) |
98 | - self.txn.commit() |
99 | - |
100 | - self.logger.info('Done!') |
101 | - |
102 | - |
103 | -if __name__ == '__main__': |
104 | - script = CreateDebWatches("debbugs-mkwatch") |
105 | - script.lock_and_run() |
106 | - |
107 | |
108 | === removed file 'cronscripts/update-debwatches.py' |
109 | --- cronscripts/update-debwatches.py 2011-06-15 15:11:43 +0000 |
110 | +++ cronscripts/update-debwatches.py 1970-01-01 00:00:00 +0000 |
111 | @@ -1,240 +0,0 @@ |
112 | -#!/usr/bin/python -S |
113 | -# |
114 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
115 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
116 | - |
117 | -# This script runs through the set of Debbugs watches, and tries to |
118 | -# syncronise each of those to the malone bug which is watching it. |
119 | - |
120 | -import _pythonpath |
121 | -import os |
122 | -import sys |
123 | -import email |
124 | -import logging |
125 | - |
126 | -# zope bits |
127 | -from zope.component import getUtility |
128 | - |
129 | -from canonical.database.constants import UTC_NOW |
130 | -from lp.app.errors import NotFoundError |
131 | -from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
132 | -from lp.bugs.interfaces.bug import IBugSet |
133 | -from lp.bugs.interfaces.bugtask import ( |
134 | - BugTaskSearchParams, |
135 | - IBugTaskSet, |
136 | - ) |
137 | -from lp.bugs.interfaces.bugwatch import IBugWatchSet |
138 | -from lp.bugs.interfaces.cve import ICveSet |
139 | -from lp.bugs.scripts import debbugs |
140 | -from lp.services.scripts.base import (LaunchpadCronScript, |
141 | - LaunchpadScriptFailure) |
142 | -from lp.services.messages.interfaces.message import ( |
143 | - InvalidEmailMessage, |
144 | - IMessageSet, |
145 | - ) |
146 | - |
147 | - |
148 | -# setup core values and defaults |
149 | -debbugs_location_default = '/srv/bugs-mirror.debian.org/' |
150 | - |
151 | - |
152 | -class DebWatchUpdater(LaunchpadCronScript): |
153 | - loglevel = logging.WARNING |
154 | - |
155 | - def add_my_options(self): |
156 | - self.parser.add_option( |
157 | - '--max', action='store', type='int', dest='max', |
158 | - default=None, help="The maximum number of bugs to synchronise.") |
159 | - self.parser.add_option('--debbugs', action='store', type='string', |
160 | - dest='debbugs', |
161 | - default=debbugs_location_default, |
162 | - help="The location of your debbugs database.") |
163 | - |
164 | - def main(self): |
165 | - if not os.path.exists( |
166 | - os.path.join(self.options.debbugs, 'index/index.db')): |
167 | - raise LaunchpadScriptFailure('%s is not a debbugs db.' |
168 | - % self.options.debbugs) |
169 | - |
170 | - self.txn.begin() |
171 | - self.debbugs_db = debbugs.Database(self.options.debbugs) |
172 | - self.sync() |
173 | - self.txn.commit() |
174 | - |
175 | - self.logger.info('Done!') |
176 | - |
177 | - def sync(self): |
178 | - changedcounter = 0 |
179 | - |
180 | - self.logger.info('Finding existing debbugs watches...') |
181 | - debbugs_tracker = getUtility(ILaunchpadCelebrities).debbugs |
182 | - debwatches = debbugs_tracker.watches |
183 | - |
184 | - previousimportset = set([b.remotebug for b in debwatches]) |
185 | - self.logger.info( |
186 | - '%d debbugs previously imported.' % len(previousimportset)) |
187 | - |
188 | - target_watches = [watch for watch in debwatches if watch.needscheck] |
189 | - self.logger.info( |
190 | - '%d debbugs watches to syncronise.' % len(target_watches)) |
191 | - |
192 | - self.logger.info('Sorting bug watches...') |
193 | - target_watches.sort(key=lambda a: a.remotebug) |
194 | - |
195 | - self.logger.info('Syncing bug watches...') |
196 | - for watch in target_watches: |
197 | - if self.sync_watch(watch): |
198 | - changedcounter += 1 |
199 | - self.txn.commit() |
200 | - if self.options.max: |
201 | - if changedcounter >= self.options.max: |
202 | - self.logger.info('Synchronised %d bugs!' % changedcounter) |
203 | - return |
204 | - |
205 | - def sync_watch(self, watch): |
206 | - # keep track of whether or not something changed |
207 | - waschanged = False |
208 | - # find the bug in malone |
209 | - malone_bug = watch.bug |
210 | - # find the bug in debbugs |
211 | - debian_bug = self.debbugs_db[int(watch.remotebug)] |
212 | - bugset = getUtility(IBugSet) |
213 | - bugtaskset = getUtility(IBugTaskSet) |
214 | - bugwatchset = getUtility(IBugWatchSet) |
215 | - messageset = getUtility(IMessageSet) |
216 | - debian = getUtility(ILaunchpadCelebrities).debian |
217 | - debbugs_tracker = getUtility(ILaunchpadCelebrities).debbugs |
218 | - |
219 | - # make sure we have tasks for all the debian package linkages, and |
220 | - # also make sure we have updated their status and severity |
221 | - # appropriately. |
222 | - for packagename in debian_bug.packagelist(): |
223 | - try: |
224 | - srcpkgname = debian.guessPublishedSourcePackageName( |
225 | - packagename) |
226 | - except NotFoundError: |
227 | - self.logger.error(sys.exc_value) |
228 | - continue |
229 | - search_params = BugTaskSearchParams(user=None, bug=malone_bug, |
230 | - sourcepackagename=srcpkgname) |
231 | - search_params.setDistribution(debian) |
232 | - bugtasks = bugtaskset.search(search_params) |
233 | - if len(bugtasks) == 0: |
234 | - # we need a new task to link the bug to the debian package |
235 | - self.logger.info('Linking %d and debian %s' % ( |
236 | - malone_bug.id, srcpkgname.name)) |
237 | - # XXX: kiko 2007-02-03: |
238 | - # This code is completely untested and broken. |
239 | - bugtask = malone_bug.addTask( |
240 | - owner=malone_bug.owner, distribution=debian, |
241 | - sourcepackagename=srcpkgname) |
242 | - bugtask.bugwatch = watch |
243 | - waschanged = True |
244 | - else: |
245 | - assert len(bugtasks) == 1, 'Should only find a single task' |
246 | - bugtask = bugtasks[0] |
247 | - status = bugtask.status |
248 | - if status != bugtask.setStatusFromDebbugs(debian_bug.status): |
249 | - waschanged = True |
250 | - severity = bugtask.severity |
251 | - if severity != bugtask.setSeverityFromDebbugs( |
252 | - debian_bug.severity): |
253 | - waschanged = True |
254 | - |
255 | - known_msg_ids = set([msg.rfc822msgid for msg in malone_bug.messages]) |
256 | - |
257 | - for raw_msg in debian_bug.comments: |
258 | - |
259 | - # parse it so we can extract the message id easily |
260 | - message = email.message_from_string(raw_msg) |
261 | - |
262 | - # see if we already have imported a message with this id for this |
263 | - # bug |
264 | - message_id = message['message-id'] |
265 | - if message_id in known_msg_ids: |
266 | - # Skipping msg that is already imported |
267 | - continue |
268 | - |
269 | - # make sure this message is in the db |
270 | - msg = None |
271 | - try: |
272 | - msg = messageset.fromEmail(raw_msg, parsed_message=message, |
273 | - create_missing_persons=True) |
274 | - except InvalidEmailMessage: |
275 | - self.logger.error('Invalid email: %s' % sys.exc_value) |
276 | - if msg is None: |
277 | - continue |
278 | - |
279 | - # Create the link between the bug and this message. |
280 | - malone_bug.linkMessage(msg) |
281 | - |
282 | - # ok, this is a new message for this bug, so in effect something |
283 | - # has changed |
284 | - waschanged = True |
285 | - |
286 | - # now we need to analyse the message for useful data |
287 | - watches = bugwatchset.fromMessage(msg, malone_bug) |
288 | - for watch in watches: |
289 | - self.logger.info( |
290 | - 'New watch for #%s on %s' % (watch.bug.id, watch.url)) |
291 | - waschanged = True |
292 | - |
293 | - # and also for CVE ref clues |
294 | - prior_cves = set(malone_bug.cves) |
295 | - cveset = getUtility(ICveSet) |
296 | - cves = cveset.inMessage(msg) |
297 | - for cve in cves: |
298 | - malone_bug.linkCVE(cve) |
299 | - if cve not in prior_cves: |
300 | - self.logger.info('CVE-%s (%s) found for Malone #%s' % ( |
301 | - cve.sequence, cve.status.name, malone_bug.id)) |
302 | - |
303 | - # now we know about this message for this bug |
304 | - known_msg_ids.add(message_id) |
305 | - |
306 | - # and best we commit, so that we can see the email that the |
307 | - # librarian has created in the db |
308 | - self.txn.commit() |
309 | - |
310 | - # Mark all merged bugs as duplicates of the lowest-numbered bug |
311 | - if (len(debian_bug.mergedwith) > 0 and |
312 | - min(debian_bug.mergedwith) > debian_bug.id): |
313 | - for merged_id in debian_bug.mergedwith: |
314 | - merged_bug = bugset.queryByRemoteBug( |
315 | - debbugs_tracker, merged_id) |
316 | - if merged_bug is not None: |
317 | - # Bug has been imported already |
318 | - if merged_bug.duplicateof == malone_bug: |
319 | - # we already know about this |
320 | - continue |
321 | - elif merged_bug.duplicateof is not None: |
322 | - # Interesting, we think it's a dup of something else |
323 | - self.logger.warning( |
324 | - 'Debbugs thinks #%d is a dup of #%d' % ( |
325 | - merged_bug.id, merged_bug.duplicateof)) |
326 | - continue |
327 | - # Go ahead and merge it |
328 | - self.logger.info( |
329 | - "Malone #%d is a duplicate of Malone #%d" % ( |
330 | - merged_bug.id, malone_bug.id)) |
331 | - merged_bug.duplicateof = malone_bug.id |
332 | - |
333 | - # the dup status has changed |
334 | - waschanged = True |
335 | - |
336 | - # make a note of the remote watch status, if it has changed |
337 | - if watch.remotestatus != debian_bug.status: |
338 | - watch.remotestatus = debian_bug.status |
339 | - waschanged = True |
340 | - |
341 | - # update the watch date details |
342 | - watch.lastchecked = UTC_NOW |
343 | - if waschanged: |
344 | - watch.lastchanged = UTC_NOW |
345 | - self.logger.info('Watch on Malone #%d changed.' % watch.bug.id) |
346 | - return waschanged |
347 | - |
348 | - |
349 | -if __name__ == '__main__': |
350 | - script = DebWatchUpdater('launchpad-debbugs-sync') |
351 | - script.lock_and_run() |
352 | |
353 | === modified file 'database/schema/comments.sql' |
354 | --- database/schema/comments.sql 2011-08-03 07:52:03 +0000 |
355 | +++ database/schema/comments.sql 2011-08-03 17:36:12 +0000 |
356 | @@ -438,6 +438,7 @@ |
357 | COMMENT ON COLUMN CodeImport.date_last_successful IS 'When this code import last succeeded. NULL if this import has never succeeded.'; |
358 | COMMENT ON COLUMN CodeImport.assignee IS 'The person in charge of delivering this code import and interacting with the owner.'; |
359 | COMMENT ON COLUMN Codeimport.update_interval IS 'How often should this import be updated. If NULL, defaults to a system-wide value set by the Launchpad administrators.'; |
360 | +COMMENT ON COLUMN CodeImport.failure_bug IS 'The bug that causes this code import to fail.'; |
361 | --COMMENT ON COLUMN CodeImport.modified_by IS 'The user modifying the CodeImport. This column is never actually set in the database -- it is only present to communicate to the trigger that creates the event, which will intercept and remove the value for this column.'; |
362 | |
363 | -- CodeImportEvent |
364 | |
365 | === added file 'database/schema/patch-2208-00-2.sql' |
366 | --- database/schema/patch-2208-00-2.sql 1970-01-01 00:00:00 +0000 |
367 | +++ database/schema/patch-2208-00-2.sql 2011-08-03 17:36:12 +0000 |
368 | @@ -0,0 +1,16 @@ |
369 | +-- Copyright 2010 Canonical Ltd. This software is licensed under the |
370 | +-- GNU Affero General Public License version 3 (see the file LICENSE). |
371 | +SET client_min_messages=ERROR; |
372 | + |
373 | +-- Add reference to bugs to code imports. |
374 | +ALTER TABLE CodeImport |
375 | + ADD COLUMN failure_bug integer; |
376 | +ALTER TABLE CodeImport |
377 | + ADD CONSTRAINT codeimport__failure_bug__fk |
378 | + FOREIGN KEY (failure_bug) REFERENCES Bug; |
379 | +-- A bug for the failure can only be linked if the import is failing (40). |
380 | +ALTER TABLE CodeImport |
381 | + ADD CONSTRAINT codeimport__failure_bug_requires_failing |
382 | + CHECK (review_status = 40 OR failure_bug IS NULL); |
383 | + |
384 | +INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 00, 2); |
385 | |
386 | === removed file 'lib/canonical/launchpad/scripts/debsync.py' |
387 | --- lib/canonical/launchpad/scripts/debsync.py 2011-06-15 15:11:43 +0000 |
388 | +++ lib/canonical/launchpad/scripts/debsync.py 1970-01-01 00:00:00 +0000 |
389 | @@ -1,201 +0,0 @@ |
390 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
391 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
392 | - |
393 | -"""Functions related to the import of Debbugs bugs into Malone.""" |
394 | - |
395 | -__all__ = [ |
396 | - 'bug_filter', |
397 | - 'do_import', |
398 | - 'import_bug', |
399 | - ] |
400 | - |
401 | -__metaclass__ = type |
402 | - |
403 | -import datetime |
404 | -import sys |
405 | - |
406 | -from zope.component import getUtility |
407 | - |
408 | -from canonical.database.sqlbase import flush_database_updates |
409 | -from lp.app.errors import NotFoundError |
410 | -from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
411 | -from lp.bugs.interfaces.bug import ( |
412 | - CreateBugParams, |
413 | - IBugSet, |
414 | - ) |
415 | -from lp.bugs.interfaces.bugwatch import IBugWatchSet |
416 | -from lp.bugs.interfaces.cve import ICveSet |
417 | -from lp.bugs.scripts import debbugs |
418 | -from lp.services.encoding import guess as ensure_unicode |
419 | -from lp.services.messages.interfaces.message import ( |
420 | - IMessageSet, |
421 | - InvalidEmailMessage, |
422 | - UnknownSender, |
423 | - ) |
424 | - |
425 | - |
426 | -def bug_filter(bug, previous_import_set, target_bugs, target_package_set, |
427 | - minimum_age): |
428 | - """Function to choose which debian bugs will get processed by the sync |
429 | - script. |
430 | - """ |
431 | - # don't re-import one that exists already |
432 | - if str(bug.id) in previous_import_set: |
433 | - return False |
434 | - # if we've been given a list, import only those |
435 | - if target_bugs: |
436 | - if bug.id in target_bugs: |
437 | - return True |
438 | - return False |
439 | - # we only want bugs in Sid |
440 | - if not bug.affects_unstable(): |
441 | - return False |
442 | - # and we only want RC bugs |
443 | - #if not bug.is_release_critical(): |
444 | - # return False |
445 | - # and we only want bugs that affect the packages we care about: |
446 | - if not bug.affects_package(target_package_set): |
447 | - return False |
448 | - # we will not import any dup bugs (any reason to?) |
449 | - if len(bug.mergedwith) > 0: |
450 | - return False |
451 | - # and we won't import any bug that is newer than one week, to give |
452 | - # debian some time to find dups |
453 | - if bug.date > datetime.datetime.now() - datetime.timedelta(minimum_age): |
454 | - return False |
455 | - return True |
456 | - |
457 | - |
458 | -def do_import(logger, max_imports, debbugs_location, target_bugs, |
459 | - target_package_set, previous_import_set, minimum_age, debbugs_pl): |
460 | - |
461 | - # figure out which bugs have been imported previously |
462 | - debbugs_tracker = getUtility(ILaunchpadCelebrities).debbugs |
463 | - for w in debbugs_tracker.watches: |
464 | - previous_import_set.add(w.remotebug) |
465 | - logger.info('%d debian bugs previously imported.' % |
466 | - len(previous_import_set)) |
467 | - |
468 | - # find the new bugs to import |
469 | - logger.info('Selecting new debian bugs...') |
470 | - debbugs_db = debbugs.Database(debbugs_location, debbugs_pl) |
471 | - debian_bugs = [] |
472 | - for debian_bug in debbugs_db: |
473 | - if bug_filter(debian_bug, previous_import_set, target_bugs, |
474 | - target_package_set, minimum_age): |
475 | - debian_bugs.append(debian_bug) |
476 | - logger.info('%d debian bugs ready to import.' % len(debian_bugs)) |
477 | - |
478 | - # put them in ascending order |
479 | - logger.info('Sorting bugs...') |
480 | - debian_bugs.sort(lambda a, b: cmp(a.id, b.id)) |
481 | - |
482 | - logger.info('Importing bugs...') |
483 | - newbugs = 0 |
484 | - for debian_bug in debian_bugs: |
485 | - newbug = import_bug(debian_bug, logger) |
486 | - if newbug is True: |
487 | - newbugs += 1 |
488 | - if max_imports: |
489 | - if newbugs >= max_imports: |
490 | - logger.info('Imported %d new bugs!' % newbugs) |
491 | - break |
492 | - |
493 | - |
494 | -def import_bug(debian_bug, logger): |
495 | - """Consider importing a debian bug, return True if you did.""" |
496 | - bugset = getUtility(IBugSet) |
497 | - debbugs_tracker = getUtility(ILaunchpadCelebrities).debbugs |
498 | - malone_bug = bugset.queryByRemoteBug(debbugs_tracker, debian_bug.id) |
499 | - if malone_bug is not None: |
500 | - logger.error('Debbugs #%d was previously imported.' % debian_bug.id) |
501 | - return False |
502 | - # get the email which started it all |
503 | - try: |
504 | - email_txt = debian_bug.comments[0] |
505 | - except IndexError: |
506 | - logger.error('No initial mail for debian #%d' % debian_bug.id) |
507 | - return False |
508 | - except debbugs.LogParseFailed, e: |
509 | - logger.warning(e) |
510 | - return False |
511 | - msg = None |
512 | - messageset = getUtility(IMessageSet) |
513 | - debian = getUtility(ILaunchpadCelebrities).debian |
514 | - ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
515 | - try: |
516 | - msg = messageset.fromEmail(email_txt, distribution=debian, |
517 | - create_missing_persons=True) |
518 | - except UnknownSender: |
519 | - logger.error('Cannot create person for %s' % sys.exc_value) |
520 | - except InvalidEmailMessage: |
521 | - logger.error('Invalid email: %s' % sys.exc_value) |
522 | - if msg is None: |
523 | - logger.error('Failed to import debian #%d' % debian_bug.id) |
524 | - return False |
525 | - |
526 | - # get the bug details |
527 | - title = debian_bug.subject |
528 | - if not title: |
529 | - title = 'Debbugs #%d with no title' % debian_bug.id |
530 | - title = ensure_unicode(title) |
531 | - # debian_bug.package may have ,-separated package names, but |
532 | - # debian_bug.packagelist[0] is going to be a single package name for |
533 | - # sure. we work through the package list, try to find one we can |
534 | - # work with, otherwise give up |
535 | - srcpkg = pkgname = None |
536 | - for pkgname in debian_bug.packagelist(): |
537 | - try: |
538 | - srcpkg = ubuntu.guessPublishedSourcePackageName(pkgname) |
539 | - except NotFoundError: |
540 | - logger.error(sys.exc_value) |
541 | - if srcpkg is None: |
542 | - # none of the package names gave us a source package we can use |
543 | - # XXX sabdfl 2005-09-16: Maybe this should just be connected to the |
544 | - # distro, and allowed to wait for re-assignment to a specific package? |
545 | - logger.error('Unable to find package details for %s' % ( |
546 | - debian_bug.package)) |
547 | - return False |
548 | - # sometimes debbugs has initial emails that contain the package name, we |
549 | - # can remove that |
550 | - if title.startswith(pkgname + ':'): |
551 | - title = title[len(pkgname) + 2:].strip() |
552 | - params = CreateBugParams( |
553 | - title=title, msg=msg, owner=msg.owner, |
554 | - datecreated=msg.datecreated) |
555 | - params.setBugTarget(distribution=debian, sourcepackagename=srcpkg) |
556 | - malone_bug = bugset.createBug(params) |
557 | - # create a debwatch for this bug |
558 | - thewatch = malone_bug.addWatch(debbugs_tracker, str(debian_bug.id), |
559 | - malone_bug.owner) |
560 | - thewatch.remotestatus = debian_bug.status |
561 | - |
562 | - # link the relevant task to this watch |
563 | - assert len(malone_bug.bugtasks) == 1, 'New bug should have only one task' |
564 | - task = malone_bug.bugtasks[0] |
565 | - task.bugwatch = thewatch |
566 | - task.setStatusFromDebbugs(debian_bug.status) |
567 | - task.setSeverityFromDebbugs(debian_bug.severity) |
568 | - |
569 | - # Let the world know about it! |
570 | - logger.info('%d/%s: %s: %s' % ( |
571 | - debian_bug.id, malone_bug.id, debian_bug.package, title)) |
572 | - |
573 | - # now we need to analyse the message for bugwatch clues |
574 | - bugwatchset = getUtility(IBugWatchSet) |
575 | - watches = bugwatchset.fromMessage(msg, malone_bug) |
576 | - for watch in watches: |
577 | - logger.info('New watch for %s on %s' % (watch.bug.id, watch.url)) |
578 | - |
579 | - # and also for CVE ref clues |
580 | - cveset = getUtility(ICveSet) |
581 | - cves = cveset.inMessage(msg) |
582 | - prior_cves = malone_bug.cves |
583 | - for cve in cves: |
584 | - if cve not in prior_cves: |
585 | - malone_bug.linkCVE(cve) |
586 | - logger.info('CVE-%s (%s) found for Malone #%s' % ( |
587 | - cve.sequence, cve.status.name, malone_bug.id)) |
588 | - |
589 | - flush_database_updates() |
590 | - return True |
591 | |
592 | === modified file 'lib/lp/bugs/browser/bugtask.py' |
593 | --- lib/lp/bugs/browser/bugtask.py 2011-08-02 05:35:39 +0000 |
594 | +++ lib/lp/bugs/browser/bugtask.py 2011-08-03 17:36:12 +0000 |
595 | @@ -3247,7 +3247,25 @@ |
596 | # iteration. |
597 | bugtasks_by_package = bug.getBugTasksByPackageName(all_bugtasks) |
598 | |
599 | + latest_parent = None |
600 | + |
601 | for bugtask in all_bugtasks: |
602 | + # Series bug targets only display the series name, so they |
603 | + # must always be preceded by their parent context. Normally |
604 | + # the parent will have a task, but if not we need to show a |
605 | + # fake one. |
606 | + if ISeriesBugTarget.providedBy(bugtask.target): |
607 | + parent = bugtask.target.bugtarget_parent |
608 | + else: |
609 | + latest_parent = parent = bugtask.target |
610 | + |
611 | + if parent != latest_parent: |
612 | + latest_parent = parent |
613 | + bugtask_and_nomination_views.append( |
614 | + getMultiAdapter( |
615 | + (parent, self.request), |
616 | + name='+bugtasks-and-nominations-table-row')) |
617 | + |
618 | conjoined_master = bugtask.getConjoinedMaster( |
619 | bugtasks, bugtasks_by_package) |
620 | view = self._getTableRowView( |
621 | |
622 | === modified file 'lib/lp/bugs/browser/configure.zcml' |
623 | --- lib/lp/bugs/browser/configure.zcml 2011-07-27 13:23:38 +0000 |
624 | +++ lib/lp/bugs/browser/configure.zcml 2011-08-03 17:36:12 +0000 |
625 | @@ -561,6 +561,11 @@ |
626 | name="+bugtasks-and-nominations-table-row" |
627 | template="../templates/bugtask-tasks-and-nominations-table-row.pt"/> |
628 | <browser:page |
629 | + for="lp.bugs.interfaces.bugtarget.IBugTarget" |
630 | + permission="zope.Public" |
631 | + name="+bugtasks-and-nominations-table-row" |
632 | + template="../templates/bugtarget-tasks-and-nominations-table-row.pt"/> |
633 | + <browser:page |
634 | for="lp.bugs.interfaces.bugtask.IBugTask" |
635 | name="+bugtask-macros-listing" |
636 | template="../templates/bugtask-macros-listing.pt" |
637 | |
638 | === modified file 'lib/lp/bugs/browser/tests/test_bugtask.py' |
639 | --- lib/lp/bugs/browser/tests/test_bugtask.py 2011-08-01 05:25:59 +0000 |
640 | +++ lib/lp/bugs/browser/tests/test_bugtask.py 2011-08-03 17:36:12 +0000 |
641 | @@ -533,6 +533,30 @@ |
642 | foo_bugtasks_and_nominations_view.getBugTaskAndNominationViews()) |
643 | self.assertEqual([], task_and_nomination_views) |
644 | |
645 | + def test_bugtarget_parent_shown_for_orphaned_series_tasks(self): |
646 | + # Test that a row is shown for the parent of a series task, even |
647 | + # if the parent doesn't actually have a task. |
648 | + series = self.factory.makeProductSeries() |
649 | + bug = self.factory.makeBug(series=series) |
650 | + self.assertEqual(2, len(bug.bugtasks)) |
651 | + new_prod = self.factory.makeProduct() |
652 | + bug.getBugTask(series.product).transitionToTarget(new_prod) |
653 | + |
654 | + view = create_initialized_view(bug, "+bugtasks-and-nominations-table") |
655 | + subviews = view.getBugTaskAndNominationViews() |
656 | + self.assertEqual([ |
657 | + (series.product, '+bugtasks-and-nominations-table-row'), |
658 | + (bug.getBugTask(series), '+bugtasks-and-nominations-table-row'), |
659 | + (bug.getBugTask(new_prod), '+bugtasks-and-nominations-table-row'), |
660 | + ], [(v.context, v.__name__) for v in subviews]) |
661 | + |
662 | + content = subviews[0]() |
663 | + self.assertIn( |
664 | + 'href="%s"' % canonical_url( |
665 | + series.product, path_only_if_possible=True), |
666 | + content) |
667 | + self.assertIn(series.product.displayname, content) |
668 | + |
669 | |
670 | class TestBugTaskEditViewStatusField(TestCaseWithFactory): |
671 | """We show only those options as possible value in the status |
672 | |
673 | === removed file 'lib/lp/bugs/doc/bugzilla-import.txt' |
674 | --- lib/lp/bugs/doc/bugzilla-import.txt 2011-06-14 20:35:20 +0000 |
675 | +++ lib/lp/bugs/doc/bugzilla-import.txt 1970-01-01 00:00:00 +0000 |
676 | @@ -1,562 +0,0 @@ |
677 | -Bugzilla Import |
678 | -=============== |
679 | - |
680 | -The bugzilla import process makes use of a direct connection to the |
681 | -database. In order to aid in testing, all the database accesses are |
682 | -performed through a single class that can be replaced. |
683 | - |
684 | -We will start by defining a fake backend and some fake information for |
685 | -it to return: |
686 | - |
687 | - >>> from datetime import datetime |
688 | - >>> import pytz |
689 | - >>> UTC = pytz.timezone('UTC') |
690 | - |
691 | - >>> users = [ |
692 | - ... ('test@canonical.com', 'Sample User'), |
693 | - ... ('foo.bar@canonical.com', 'Foo Bar'), |
694 | - ... ('new.user@canonical.com', 'New User') # <- not in Launchpad |
695 | - ... ] |
696 | - |
697 | - >>> buginfo = [ |
698 | - ... (1, # bug_id |
699 | - ... 1, # assigned_to |
700 | - ... '', # bug_file_loc |
701 | - ... 'normal', # bug_severity |
702 | - ... 'NEW', # status |
703 | - ... datetime(2005, 4, 1, tzinfo=UTC), # creation |
704 | - ... 'Test bug 1', # short_desc, |
705 | - ... 'Linux', # op_sys |
706 | - ... 'P2', # priority |
707 | - ... 'Ubuntu', # product |
708 | - ... 'AMD64', # rep_platform |
709 | - ... 1, # reporter |
710 | - ... '---', # version |
711 | - ... 'mozilla-firefox', # component |
712 | - ... '', # resolution |
713 | - ... 'Ubuntu 5.10', # milestone |
714 | - ... 0, # qa_contact |
715 | - ... 'status', # status_whiteboard |
716 | - ... '', # keywords |
717 | - ... ''), # alias |
718 | - ... # A WONTFIX bug on a non-existant distro package |
719 | - ... (2, 1, 'http://www.ubuntu.com', 'enhancement', 'RESOLVED', |
720 | - ... datetime(2005, 4, 2, tzinfo=UTC), 'Test bug 2', |
721 | - ... 'Linux', 'P1', 'Ubuntu', 'i386', 2, '---', 'unknown', |
722 | - ... 'WONTFIX', '---', 0, '', '', ''), |
723 | - ... # An accepted bug: |
724 | - ... (3, 2, 'http://www.ubuntu.com', 'blocker', 'ASSIGNED', |
725 | - ... datetime(2005, 4, 3, tzinfo=UTC), 'Test bug 3', |
726 | - ... 'Linux', 'P1', 'Ubuntu', 'i386', 1, '---', 'netapplet', |
727 | - ... '', '---', 0, '', '', 'xyz'), |
728 | - ... # A fixed bug |
729 | - ... (4, 1, 'http://www.ubuntu.com', 'blocker', 'CLOSED', |
730 | - ... datetime(2005, 4, 4, tzinfo=UTC), 'Test bug 4', |
731 | - ... 'Linux', 'P1', 'Ubuntu', 'i386', 1, '---', 'mozilla-firefox', |
732 | - ... 'FIXED', '---', 0, '', '', 'FooBar'), |
733 | - ... # An UPSTREAM bug |
734 | - ... (5, 1, |
735 | - ... 'http://bugzilla.gnome.org/bugs/show_bug.cgi?id=273041', |
736 | - ... 'blocker', 'UPSTREAM', |
737 | - ... datetime(2005, 4, 4, tzinfo=UTC), 'Test bug 5', |
738 | - ... 'Linux', 'P1', 'Ubuntu', 'i386', 1, '---', 'evolution', |
739 | - ... '', '---', 0, '', '', 'deb1234'), |
740 | - ... ] |
741 | - |
742 | - >>> ccs = [[], [3], [], [], []] |
743 | - |
744 | - >>> comments = [ |
745 | - ... [(1, datetime(2005, 4, 1, tzinfo=UTC), 'First comment'), |
746 | - ... (2, datetime(2005, 4, 1, 1, tzinfo=UTC), 'Second comment')], |
747 | - ... [(1, datetime(2005, 4, 2, tzinfo=UTC), 'First comment'), |
748 | - ... (2, datetime(2005, 4, 2, 1, tzinfo=UTC), 'Second comment')], |
749 | - ... [(2, datetime(2005, 4, 3, tzinfo=UTC), 'First comment'), |
750 | - ... (1, datetime(2005, 4, 3, 1, tzinfo=UTC), |
751 | - ... 'This is related to CVE-2005-1234'), |
752 | - ... (2, datetime(2005, 4, 3, 2, tzinfo=UTC), |
753 | - ... 'Created an attachment (id=1)')], |
754 | - ... [(1, datetime(2005, 4, 4, tzinfo=UTC), 'First comment')], |
755 | - ... [(1, datetime(2005, 4, 5, tzinfo=UTC), 'First comment')], |
756 | - ... ] |
757 | - |
758 | - >>> attachments = [ |
759 | - ... [], [], |
760 | - ... [(1, datetime(2005, 4, 3, 2, tzinfo=UTC), 'An attachment', |
761 | - ... 'text/x-patch', True, 'foo.patch', 'the data', 2)], |
762 | - ... [], [] |
763 | - ... ] |
764 | - |
765 | - >>> duplicates = [ |
766 | - ... (1, 2), |
767 | - ... (3, 4), |
768 | - ... ] |
769 | - |
770 | - >>> class FakeBackend: |
771 | - ... def lookupUser(self, user_id): |
772 | - ... return users[user_id - 1] |
773 | - ... def getBugInfo(self, bug_id): |
774 | - ... return buginfo[bug_id - 1] |
775 | - ... def getBugCcs(self, bug_id): |
776 | - ... return ccs[bug_id - 1] |
777 | - ... def getBugComments(self, bug_id): |
778 | - ... return comments[bug_id - 1] |
779 | - ... def getBugAttachments(self, bug_id): |
780 | - ... return attachments[bug_id - 1] |
781 | - ... def getDuplicates(self): |
782 | - ... return duplicates |
783 | - |
784 | - >>> from itertools import chain |
785 | - >>> from zope.component import getUtility |
786 | - >>> from canonical.launchpad.ftests import login |
787 | - >>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
788 | - >>> from lp.bugs.interfaces.bug import IBugSet |
789 | - >>> from lp.bugs.scripts import bugzilla |
790 | - >>> from lp.registry.interfaces.person import IPersonSet |
791 | - |
792 | -Get a reference to the Ubuntu bug tracker, and log in: |
793 | - |
794 | - >>> login('bug-importer@launchpad.net') |
795 | - >>> bugtracker = getUtility(ILaunchpadCelebrities).ubuntu_bugzilla |
796 | - |
797 | -Now we create a bugzilla.Bugzilla instance to handle the import, using |
798 | -our fake backend data: |
799 | - |
800 | - >>> bz = bugzilla.Bugzilla(None) |
801 | - >>> bz.backend = FakeBackend() |
802 | - |
803 | -In order to verify that things get imported correctly, the following |
804 | -function will be used: |
805 | - |
806 | - >>> def bugInfo(bug): |
807 | - ... print 'Title:', bug.title |
808 | - ... print 'Reporter:', bug.owner.displayname |
809 | - ... print 'Created:', bug.datecreated |
810 | - ... if bug.name: |
811 | - ... print 'Nick: %s' % bug.name |
812 | - ... print 'Subscribers:' |
813 | - ... subscriber_names = sorted( |
814 | - ... p.displayname for p in chain( |
815 | - ... bug.getDirectSubscribers(), |
816 | - ... bug.getIndirectSubscribers())) |
817 | - ... for subscriber_name in subscriber_names: |
818 | - ... print ' %s' % subscriber_name |
819 | - ... for task in bug.bugtasks: |
820 | - ... print 'Task:', task.bugtargetdisplayname |
821 | - ... print ' Status:', task.status.name |
822 | - ... if task.product: |
823 | - ... print ' Product:', task.product.name |
824 | - ... if task.distribution: |
825 | - ... print ' Distro:', task.distribution.name |
826 | - ... if task.sourcepackagename: |
827 | - ... print ' Source package:', task.sourcepackagename.name |
828 | - ... if task.assignee: |
829 | - ... print ' Assignee:', task.assignee.displayname |
830 | - ... if task.importance: |
831 | - ... print ' Importance:', task.importance.name |
832 | - ... if task.statusexplanation: |
833 | - ... print ' Explanation:', task.statusexplanation |
834 | - ... if task.milestone: |
835 | - ... print ' Milestone:', task.milestone.name |
836 | - ... if task.bugwatch: |
837 | - ... print ' Watch:', task.bugwatch.url |
838 | - ... if bug.cves: |
839 | - ... print 'CVEs:' |
840 | - ... for cve in bug.cves: |
841 | - ... print ' %s' % cve.displayname |
842 | - ... print 'Messages:' |
843 | - ... for message in bug.messages: |
844 | - ... print ' Author:', message.owner.displayname |
845 | - ... print ' Date:', message.datecreated |
846 | - ... print ' Subject:', message.subject |
847 | - ... print ' %s' % message.text_contents |
848 | |
849 | - ... if bug.attachments.any(): |
850 | - ... print 'Attachments:' |
851 | - ... for attachment in bug.attachments: |
852 | - ... print ' Title:', attachment.title |
853 | - ... print ' Type:', attachment.type.name |
854 | - ... print ' Name:', attachment.libraryfile.filename |
855 | - ... print ' Mime type:', attachment.libraryfile.mimetype |
856 | - |
857 | - |
858 | -Now we import bug #1 and check the results: |
859 | - |
860 | - >>> bug = bz.handleBug(1) |
861 | - >>> bugInfo(bug) |
862 | - Title: Test bug 1 |
863 | - Reporter: Sample Person |
864 | - Created: 2005-04-01 00:00:00+00:00 |
865 | - Subscribers: |
866 | - Foo Bar |
867 | - Sample Person |
868 | - Ubuntu Team |
869 | - Task: mozilla-firefox (Ubuntu) |
870 | - Status: NEW |
871 | - Distro: ubuntu |
872 | - Source package: mozilla-firefox |
873 | - Assignee: Sample Person |
874 | - Importance: MEDIUM |
875 | - Explanation: status (Bugzilla status=NEW, product=Ubuntu, |
876 | - component=mozilla-firefox) |
877 | - Milestone: ubuntu-5.10 |
878 | - Messages: |
879 | - Author: Sample Person |
880 | - Date: 2005-04-01 00:00:00+00:00 |
881 | - Subject: Test bug 1 |
882 | - First comment |
883 | - <BLANKLINE> |
884 | - Author: Foo Bar |
885 | - Date: 2005-04-01 01:00:00+00:00 |
886 | - Subject: Re: Test bug 1 |
887 | - Second comment |
888 | - <BLANKLINE> |
889 | - |
890 | -As well as importing the bug, a bug watch is created, linking the new |
891 | -Launchpad bug to the original Bugzilla bug: |
892 | - |
893 | - >>> linked_bug = getUtility(IBugSet).queryByRemoteBug(bugtracker, 1) |
894 | - >>> linked_bug == bug |
895 | - True |
896 | - |
897 | -This bug watch link is used to prevent multiple imports of the same |
898 | -bug. |
899 | - |
900 | - >>> second_import = bz.handleBug(1) |
901 | - >>> bug == second_import |
902 | - True |
903 | - |
904 | - |
905 | -Next we try bug #2, which is assigned to a non-existant source |
906 | -package, so gets filed directly against the distribution. Some things |
907 | -to notice: |
908 | - |
909 | - * A Launchpad account is created for new.user@canonical.com as a side |
910 | - effect of the import, because they are subscribed to the bug. |
911 | - * The "RESOLVED WONTFIX" status is converted to a status of INVALID. |
912 | - * The fact that the "unknown" package does not exist in Ubuntu has |
913 | - been logged, along with the exception raised by |
914 | - guessPublishedSourcePackageName(). |
915 | - |
916 | - >>> print getUtility(IPersonSet).getByEmail('new.user@canonical.com') |
917 | - None |
918 | - >>> bug = bz.handleBug(2) |
919 | - WARNING:lp.bugs.scripts.bugzilla:could not find package name for |
920 | - "unknown": 'Unknown package: unknown' |
921 | - >>> import transaction |
922 | - >>> transaction.commit() |
923 | - |
924 | - >>> bugInfo(bug) |
925 | - Title: Test bug 2 |
926 | - Reporter: Foo Bar |
927 | - Created: 2005-04-02 00:00:00+00:00 |
928 | - Subscribers: |
929 | - Foo Bar |
930 | - New User |
931 | - Sample Person |
932 | - Ubuntu Team |
933 | - Task: Ubuntu |
934 | - Status: INVALID |
935 | - Distro: ubuntu |
936 | - Assignee: Sample Person |
937 | - Importance: WISHLIST |
938 | - Explanation: Bugzilla status=RESOLVED WONTFIX, product=Ubuntu, |
939 | - component=unknown |
940 | - Messages: |
941 | - Author: Sample Person |
942 | - Date: 2005-04-02 00:00:00+00:00 |
943 | - Subject: Test bug 2 |
944 | - First comment |
945 | - <BLANKLINE> |
946 | - http://www.ubuntu.com |
947 | - <BLANKLINE> |
948 | - Author: Foo Bar |
949 | - Date: 2005-04-02 01:00:00+00:00 |
950 | - Subject: Re: Test bug 2 |
951 | - Second comment |
952 | - <BLANKLINE> |
953 | - >>> getUtility(IPersonSet).getByEmail('new.user@canonical.com') |
954 | - <Person at ...> |
955 | - |
956 | - |
957 | -Now import an ASSIGNED bug. Things to note about this import: |
958 | - |
959 | - * the second comment mentions a CVE, causing a link between the bug |
960 | - and CVE to be established. |
961 | - * The attachment on this bug is imported |
962 | - |
963 | - >>> bug = bz.handleBug(3) |
964 | - >>> bugInfo(bug) |
965 | - Title: Test bug 3 |
966 | - Reporter: Sample Person |
967 | - Created: 2005-04-03 00:00:00+00:00 |
968 | - Nick: xyz |
969 | - Subscribers: |
970 | - Foo Bar |
971 | - Sample Person |
972 | - Ubuntu Team |
973 | - Task: netapplet (Ubuntu) |
974 | - Status: CONFIRMED |
975 | - Distro: ubuntu |
976 | - Source package: netapplet |
977 | - Assignee: Foo Bar |
978 | - Importance: CRITICAL |
979 | - Explanation: Bugzilla status=ASSIGNED, product=Ubuntu, |
980 | - component=netapplet |
981 | - CVEs: |
982 | - CVE-2005-1234 |
983 | - Messages: |
984 | - Author: Foo Bar |
985 | - Date: 2005-04-03 00:00:00+00:00 |
986 | - Subject: Test bug 3 |
987 | - First comment |
988 | - <BLANKLINE> |
989 | - http://www.ubuntu.com |
990 | - <BLANKLINE> |
991 | - Author: Sample Person |
992 | - Date: 2005-04-03 01:00:00+00:00 |
993 | - Subject: Re: Test bug 3 |
994 | - This is related to CVE-2005-1234 |
995 | - <BLANKLINE> |
996 | - Author: Foo Bar |
997 | - Date: 2005-04-03 02:00:00+00:00 |
998 | - Subject: Re: Test bug 3 |
999 | - Created an attachment (id=1) |
1000 | - <BLANKLINE> |
1001 | - Attachments: |
1002 | - Title: An attachment |
1003 | - Type: PATCH |
1004 | - Name: foo.patch |
1005 | - Mime type: text/plain |
1006 | - |
1007 | - |
1008 | -Next we import a fixed bug: |
1009 | - |
1010 | - >>> bug = bz.handleBug(4) |
1011 | - >>> bugInfo(bug) |
1012 | - Title: Test bug 4 |
1013 | - Reporter: Sample Person |
1014 | - Created: 2005-04-04 00:00:00+00:00 |
1015 | - Nick: foobar |
1016 | - Subscribers: |
1017 | - Foo Bar |
1018 | - Sample Person |
1019 | - Ubuntu Team |
1020 | - Task: mozilla-firefox (Ubuntu) |
1021 | - Status: FIXRELEASED |
1022 | - Distro: ubuntu |
1023 | - Source package: mozilla-firefox |
1024 | - Assignee: Sample Person |
1025 | - Importance: CRITICAL |
1026 | - Explanation: Bugzilla status=CLOSED FIXED, product=Ubuntu, |
1027 | - component=mozilla-firefox |
1028 | - Messages: |
1029 | - Author: Sample Person |
1030 | - Date: 2005-04-04 00:00:00+00:00 |
1031 | - Subject: Test bug 4 |
1032 | - First comment |
1033 | - <BLANKLINE> |
1034 | - http://www.ubuntu.com |
1035 | - <BLANKLINE> |
1036 | - |
1037 | - |
1038 | -The Ubuntu bugzilla uses the UPSTREAM state to categorise bugs that |
1039 | -have been forwarded on to the upstream developers. Usually the |
1040 | -upstream bug tracker URL is included in the URL field of the bug. |
1041 | - |
1042 | -The Malone equivalent of this is to create a second task on the bug, |
1043 | -and attach a watch to the upstream bug tracker: |
1044 | - |
1045 | - # Make sane data to play this test. |
1046 | - >>> from zope.component import getUtility |
1047 | - >>> from lp.registry.interfaces.distribution import IDistributionSet |
1048 | - >>> debian = getUtility(IDistributionSet).getByName('debian') |
1049 | - >>> evolution_dsp = debian.getSourcePackage('evolution') |
1050 | - >>> ignore = factory.makeSourcePackagePublishingHistory( |
1051 | - ... distroseries=debian.currentseries, |
1052 | - ... sourcepackagename=evolution_dsp.sourcepackagename) |
1053 | - >>> transaction.commit() |
1054 | - |
1055 | - >>> bug = bz.handleBug(5) |
1056 | - >>> bugInfo(bug) |
1057 | - Title: Test bug 5 |
1058 | - Reporter: Sample Person |
1059 | - Created: 2005-04-04 00:00:00+00:00 |
1060 | - Subscribers: |
1061 | - Sample Person |
1062 | - Ubuntu Team |
1063 | - Task: Evolution |
1064 | - Status: NEW |
1065 | - Product: evolution |
1066 | - Importance: UNDECIDED |
1067 | - Watch: http://bugzilla.gnome.org/bugs/show_bug.cgi?id=273041 |
1068 | - Task: evolution (Ubuntu) |
1069 | - Status: NEW |
1070 | - Distro: ubuntu |
1071 | - Source package: evolution |
1072 | - Assignee: Sample Person |
1073 | - Importance: CRITICAL |
1074 | - Explanation: Bugzilla status=UPSTREAM, product=Ubuntu, |
1075 | - component=evolution |
1076 | - Task: evolution (Debian) |
1077 | - Status: NEW |
1078 | - Distro: debian |
1079 | - Source package: evolution |
1080 | - Importance: UNDECIDED |
1081 | - Watch: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234 |
1082 | - Messages: |
1083 | - Author: Sample Person |
1084 | - Date: 2005-04-05 00:00:00+00:00 |
1085 | - Subject: Test bug 5 |
1086 | - First comment |
1087 | - <BLANKLINE> |
1088 | - http://bugzilla.gnome.org/bugs/show_bug.cgi?id=273041 |
1089 | - <BLANKLINE> |
1090 | - |
1091 | -XXX mpt 20060404: In sampledata Evolution uses Malone officially, so adding |
1092 | -a watch to its external bug tracker is a bad example. |
1093 | - |
1094 | - |
1095 | -Severity Mapping |
1096 | ----------------- |
1097 | - |
1098 | -Bugzilla severities are mapped to the equivalent Launchpad importance values: |
1099 | - |
1100 | - >>> bug = bugzilla.Bug(bz.backend, 1) |
1101 | - >>> class FakeBugTask: |
1102 | - ... def transitionToStatus(self, status, user): |
1103 | - ... self.status = status |
1104 | - ... def transitionToImportance(self, importance, user): |
1105 | - ... self.importance = importance |
1106 | - >>> bugtask = FakeBugTask() |
1107 | - >>> for severity in ['blocker', 'critical', 'major', 'normal', |
1108 | - ... 'minor', 'trivial', 'enhancement']: |
1109 | - ... bug.bug_severity = severity |
1110 | - ... bug.mapSeverity(bugtask) |
1111 | - ... print '%-11s %s' % (severity, bugtask.importance.name) |
1112 | - blocker CRITICAL |
1113 | - critical CRITICAL |
1114 | - major HIGH |
1115 | - normal MEDIUM |
1116 | - minor LOW |
1117 | - trivial LOW |
1118 | - enhancement WISHLIST |
1119 | - |
1120 | - |
1121 | -Status Mapping |
1122 | --------------- |
1123 | - |
1124 | - >>> for status in ['UNCONFIRMED', 'NEW', 'ASSIGNED', 'REOPENED', |
1125 | - ... 'NEEDINFO', 'UPSTREAM', 'PENDINGUPLOAD', |
1126 | - ... 'RESOLVED', 'VERIFIED', 'CLOSED']: |
1127 | - ... bug.bug_status = status |
1128 | - ... bugtask.statusexplanation = '' |
1129 | - ... bug.mapStatus(bugtask) |
1130 | - ... print '%-13s %s' % (status, bugtask.status.name) |
1131 | - UNCONFIRMED NEW |
1132 | - NEW NEW |
1133 | - ASSIGNED CONFIRMED |
1134 | - REOPENED NEW |
1135 | - NEEDINFO INCOMPLETE |
1136 | - UPSTREAM NEW |
1137 | - PENDINGUPLOAD FIXCOMMITTED |
1138 | - RESOLVED INVALID |
1139 | - VERIFIED INVALID |
1140 | - CLOSED INVALID |
1141 | - |
1142 | -(note that RESOLVED, VERIFIED and CLOSED have been mapped to INVALID |
1143 | -here because the Bugzilla resolution is set to WONTFIX). |
1144 | - |
1145 | - |
1146 | -If the bug has been resolved, the resolution will affect the status: |
1147 | - |
1148 | - >>> bug.priority = 'P2' |
1149 | - >>> bug.bug_status = 'RESOLVED' |
1150 | - >>> for resolution in ['FIXED', 'INVALID', 'WONTFIX', 'NOTABUG', |
1151 | - ... 'NOTWARTY', 'UNIVERSE', 'LATER', 'REMIND', |
1152 | - ... 'DUPLICATE', 'WORKSFORME', 'MOVED']: |
1153 | - ... bug.resolution = resolution |
1154 | - ... bugtask.statusexplanation = '' |
1155 | - ... bug.mapStatus(bugtask) |
1156 | - ... print '%-10s %s' % (resolution, bugtask.status.name) |
1157 | - FIXED FIXRELEASED |
1158 | - INVALID INVALID |
1159 | - WONTFIX INVALID |
1160 | - NOTABUG INVALID |
1161 | - NOTWARTY INVALID |
1162 | - UNIVERSE INVALID |
1163 | - LATER INVALID |
1164 | - REMIND INVALID |
1165 | - DUPLICATE INVALID |
1166 | - WORKSFORME INVALID |
1167 | - MOVED INVALID |
1168 | - |
1169 | - |
1170 | -Bug Target Mapping |
1171 | ------------------- |
1172 | - |
1173 | -The Bugzilla.getLaunchpadTarget() method is used to map bugzilla bugs |
1174 | -to Launchpad bug targets. This is not general purpose logic: it only |
1175 | -applies to the Ubuntu bugzilla. |
1176 | - |
1177 | -The current mapping only handles bugs filed under the "Ubuntu" |
1178 | -product. If the component the bug is filed under is a known package |
1179 | -name, the bug is targeted at that package in ubuntu. If it isn't, |
1180 | -then the bug is filed directly against the distribution. |
1181 | - |
1182 | - >>> def showMapping(product, component): |
1183 | - ... bug.product = product |
1184 | - ... bug.component = component |
1185 | - ... target = bz.getLaunchpadBugTarget(bug) |
1186 | - ... distribution = target.get('distribution') |
1187 | - ... if distribution: |
1188 | - ... print 'Distribution:', distribution.name |
1189 | - ... spn = target.get('sourcepackagename') |
1190 | - ... if spn: |
1191 | - ... print 'Source package:', spn.name |
1192 | - ... product = target.get('product') |
1193 | - ... if product: |
1194 | - ... print 'Product:', product.name |
1195 | - |
1196 | - >>> showMapping('Ubuntu', 'mozilla-firefox') |
1197 | - Distribution: ubuntu |
1198 | - Source package: mozilla-firefox |
1199 | - |
1200 | - >>> showMapping('Ubuntu', 'netapplet') |
1201 | - Distribution: ubuntu |
1202 | - Source package: netapplet |
1203 | - |
1204 | - >>> showMapping('Ubuntu', 'unknown-package-name') |
1205 | - WARNING:lp.bugs.scripts.bugzilla:could not find package name for |
1206 | - "unknown-package-name": 'Unknown package: unknown-package-name' |
1207 | - Distribution: ubuntu |
1208 | - |
1209 | - >>> showMapping('not-Ubuntu', 'general') |
1210 | - Traceback (most recent call last): |
1211 | - ... |
1212 | - AssertionError: product must be Ubuntu |
1213 | - |
1214 | - |
1215 | -Duplicate Bug Handling |
1216 | ----------------------- |
1217 | - |
1218 | -The Bugzilla duplicate bugs table can be used to mark the |
1219 | -corresponding Launchpad bugs as duplicates too: |
1220 | - |
1221 | - >>> from lp.testing.faketransaction import FakeTransaction |
1222 | - >>> bz.processDuplicates(FakeTransaction()) |
1223 | - |
1224 | -Now check that the bugs have been marked duplicate: |
1225 | - |
1226 | - >>> bug1 = getUtility(IBugSet).queryByRemoteBug(bugtracker, 1) |
1227 | - >>> bug2 = getUtility(IBugSet).queryByRemoteBug(bugtracker, 2) |
1228 | - >>> bug3 = getUtility(IBugSet).queryByRemoteBug(bugtracker, 3) |
1229 | - >>> bug4 = getUtility(IBugSet).queryByRemoteBug(bugtracker, 4) |
1230 | - |
1231 | - >>> print bug1.duplicateof |
1232 | - None |
1233 | - >>> bug2.duplicateof == bug1 |
1234 | - True |
1235 | - >>> bug3.duplicateof == None |
1236 | - True |
1237 | - >>> bug4.duplicateof == bug3 |
1238 | - True |
1239 | |
1240 | === removed file 'lib/lp/bugs/scripts/bugzilla.py' |
1241 | --- lib/lp/bugs/scripts/bugzilla.py 2011-08-02 01:17:15 +0000 |
1242 | +++ lib/lp/bugs/scripts/bugzilla.py 1970-01-01 00:00:00 +0000 |
1243 | @@ -1,687 +0,0 @@ |
1244 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
1245 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1246 | - |
1247 | -"""Bugzilla to Launchpad import logic""" |
1248 | - |
1249 | - |
1250 | -# Bugzilla schema: |
1251 | -# http://lxr.mozilla.org/mozilla/source/webtools/bugzilla/Bugzilla/DB/Schema.pm |
1252 | - |
1253 | -# XXX: jamesh 2005-10-18 |
1254 | -# Currently unhandled bug info: |
1255 | -# * Operating system and platform |
1256 | -# * version (not really used in Ubuntu bugzilla though) |
1257 | -# * keywords |
1258 | -# * private bugs (none of the canonical-only bugs seem sensitive though) |
1259 | -# * bug dependencies |
1260 | -# * "bug XYZ" references inside comment text (at the moment we just |
1261 | -# insert the full URL to the bug afterwards). |
1262 | -# |
1263 | -# Not all of these are necessary though |
1264 | - |
1265 | -__metaclass__ = type |
1266 | - |
1267 | -from cStringIO import StringIO |
1268 | -import datetime |
1269 | -import logging |
1270 | -import re |
1271 | - |
1272 | -import pytz |
1273 | -from storm.store import Store |
1274 | -from zope.component import getUtility |
1275 | - |
1276 | -from canonical.launchpad.interfaces.emailaddress import IEmailAddressSet |
1277 | -from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
1278 | -from canonical.launchpad.webapp import canonical_url |
1279 | -from lp.app.errors import NotFoundError |
1280 | -from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
1281 | -from lp.bugs.interfaces.bug import ( |
1282 | - CreateBugParams, |
1283 | - IBugSet, |
1284 | - ) |
1285 | -from lp.bugs.interfaces.bugattachment import ( |
1286 | - BugAttachmentType, |
1287 | - IBugAttachmentSet, |
1288 | - ) |
1289 | -from lp.bugs.interfaces.bugtask import ( |
1290 | - BugTaskImportance, |
1291 | - BugTaskStatus, |
1292 | - IBugTaskSet, |
1293 | - ) |
1294 | -from lp.bugs.interfaces.bugwatch import IBugWatchSet |
1295 | -from lp.bugs.interfaces.cve import ICveSet |
1296 | -from lp.registry.interfaces.person import ( |
1297 | - IPersonSet, |
1298 | - PersonCreationRationale, |
1299 | - ) |
1300 | -from lp.services.messages.interfaces.message import IMessageSet |
1301 | - |
1302 | - |
1303 | -logger = logging.getLogger('lp.bugs.scripts.bugzilla') |
1304 | - |
1305 | - |
1306 | -def _add_tz(dt): |
1307 | - """Convert a naiive datetime value to a UTC datetime value.""" |
1308 | - assert dt.tzinfo is None, 'add_tz() only accepts naiive datetime values' |
1309 | - return datetime.datetime(dt.year, dt.month, dt.day, |
1310 | - dt.hour, dt.minute, dt.second, |
1311 | - dt.microsecond, tzinfo=pytz.timezone('UTC')) |
1312 | - |
1313 | - |
1314 | -class BugzillaBackend: |
1315 | - """A wrapper for all the MySQL database access. |
1316 | - |
1317 | - The main purpose of this is to make it possible to test the rest |
1318 | - of the import code without access to a MySQL database. |
1319 | - """ |
1320 | - def __init__(self, conn, charset='UTF-8'): |
1321 | - self.conn = conn |
1322 | - self.cursor = conn.cursor() |
1323 | - self.charset = charset |
1324 | - |
1325 | - def _decode(self, s): |
1326 | - if s is not None: |
1327 | - value = s.decode(self.charset, 'replace') |
1328 | - # Postgres doesn't like values outside of the basic multilingual |
1329 | - # plane (U+0000 - U+FFFF), so replace them (and surrogates) with |
1330 | - # U+FFFD (replacement character). |
1331 | - # Existance of these characters generally indicate an encoding |
1332 | - # problem in the existing Bugzilla data. |
1333 | - return re.sub(u'[^\u0000-\ud7ff\ue000-\uffff]', u'\ufffd', value) |
1334 | - else: |
1335 | - return None |
1336 | - |
1337 | - def lookupUser(self, user_id): |
1338 | - """Look up information about a particular Bugzilla user ID""" |
1339 | - self.cursor.execute('SELECT login_name, realname ' |
1340 | - ' FROM profiles ' |
1341 | - ' WHERE userid = %d' % user_id) |
1342 | - if self.cursor.rowcount != 1: |
1343 | - raise NotFoundError('could not look up user %d' % user_id) |
1344 | - (login_name, realname) = self.cursor.fetchone() |
1345 | - realname = self._decode(realname) |
1346 | - return (login_name, realname) |
1347 | - |
1348 | - def getBugInfo(self, bug_id): |
1349 | - """Retrieve information about a bug.""" |
1350 | - self.cursor.execute( |
1351 | - 'SELECT bug_id, assigned_to, bug_file_loc, bug_severity, ' |
1352 | - ' bug_status, creation_ts, short_desc, op_sys, priority, ' |
1353 | - ' products.name, rep_platform, reporter, version, ' |
1354 | - ' components.name, resolution, target_milestone, qa_contact, ' |
1355 | - ' status_whiteboard, keywords, alias ' |
1356 | - ' FROM bugs ' |
1357 | - ' INNER JOIN products ON bugs.product_id = products.id ' |
1358 | - ' INNER JOIN components ON bugs.component_id = components.id ' |
1359 | - ' WHERE bug_id = %d' % bug_id) |
1360 | - if self.cursor.rowcount != 1: |
1361 | - raise NotFoundError('could not look up bug %d' % bug_id) |
1362 | - (bug_id, assigned_to, bug_file_loc, bug_severity, bug_status, |
1363 | - creation_ts, short_desc, op_sys, priority, product, |
1364 | - rep_platform, reporter, version, component, resolution, |
1365 | - target_milestone, qa_contact, status_whiteboard, keywords, |
1366 | - alias) = self.cursor.fetchone() |
1367 | - |
1368 | - bug_file_loc = self._decode(bug_file_loc) |
1369 | - creation_ts = _add_tz(creation_ts) |
1370 | - product = self._decode(product) |
1371 | - version = self._decode(version) |
1372 | - component = self._decode(component) |
1373 | - status_whiteboard = self._decode(status_whiteboard) |
1374 | - keywords = self._decode(keywords) |
1375 | - alias = self._decode(alias) |
1376 | - |
1377 | - return (bug_id, assigned_to, bug_file_loc, bug_severity, |
1378 | - bug_status, creation_ts, short_desc, op_sys, priority, |
1379 | - product, rep_platform, reporter, version, component, |
1380 | - resolution, target_milestone, qa_contact, |
1381 | - status_whiteboard, keywords, alias) |
1382 | - |
1383 | - def getBugCcs(self, bug_id): |
1384 | - """Get the IDs of the people CC'd to the bug.""" |
1385 | - self.cursor.execute('SELECT who FROM cc WHERE bug_id = %d' |
1386 | - % bug_id) |
1387 | - return [row[0] for row in self.cursor.fetchall()] |
1388 | - |
1389 | - def getBugComments(self, bug_id): |
1390 | - """Get the comments for the bug.""" |
1391 | - self.cursor.execute('SELECT who, bug_when, thetext ' |
1392 | - ' FROM longdescs ' |
1393 | - ' WHERE bug_id = %d ' |
1394 | - ' ORDER BY bug_when' % bug_id) |
1395 | - # XXX: jamesh 2005-12-07: |
1396 | - # Due to a bug in Debzilla, Ubuntu bugzilla bug 248 has > 7800 |
1397 | - # duplicate comments,consisting of someone's signature. |
1398 | - # For the import, just ignore those comments. |
1399 | - return [(who, _add_tz(when), self._decode(thetext)) |
1400 | - for (who, when, thetext) in self.cursor.fetchall() |
1401 | - if thetext != '\n--=20\n Jacobo Tarr=EDo | ' |
1402 | - 'http://jacobo.tarrio.org/\n\n\n'] |
1403 | - |
1404 | - def getBugAttachments(self, bug_id): |
1405 | - """Get the attachments for the bug.""" |
1406 | - self.cursor.execute('SELECT attach_id, creation_ts, description, ' |
1407 | - ' mimetype, ispatch, filename, thedata, ' |
1408 | - ' submitter_id ' |
1409 | - ' FROM attachments ' |
1410 | - ' WHERE bug_id = %d ' |
1411 | - ' ORDER BY attach_id' % bug_id) |
1412 | - return [(attach_id, _add_tz(creation_ts), |
1413 | - self._decode(description), mimetype, |
1414 | - ispatch, self._decode(filename), thedata, submitter_id) |
1415 | - for (attach_id, creation_ts, description, |
1416 | - mimetype, ispatch, filename, thedata, |
1417 | - submitter_id) in self.cursor.fetchall()] |
1418 | - |
1419 | - def findBugs(self, product=None, component=None, status=None): |
1420 | - """Returns the requested bug IDs as a list""" |
1421 | - if product is None: |
1422 | - product = [] |
1423 | - if component is None: |
1424 | - component = [] |
1425 | - if status is None: |
1426 | - status = [] |
1427 | - joins = [] |
1428 | - conditions = [] |
1429 | - if product: |
1430 | - joins.append( |
1431 | - 'INNER JOIN products ON bugs.product_id = products.id') |
1432 | - conditions.append('products.name IN (%s)' % |
1433 | - ', '.join([self.conn.escape(p) for p in product])) |
1434 | - if component: |
1435 | - joins.append( |
1436 | - 'INNER JOIN components ON bugs.component_id = components.id') |
1437 | - conditions.append('components.name IN (%s)' % |
1438 | - ', '.join([self.conn.escape(c) for c in component])) |
1439 | - if status: |
1440 | - conditions.append('bugs.bug_status IN (%s)' % |
1441 | - ', '.join([self.conn.escape(s) for s in status])) |
1442 | - if conditions: |
1443 | - conditions = 'WHERE %s' % ' AND '.join(conditions) |
1444 | - else: |
1445 | - conditions = '' |
1446 | - self.cursor.execute('SELECT bug_id FROM bugs %s %s ORDER BY bug_id' % |
1447 | - (' '.join(joins), conditions)) |
1448 | - return [bug_id for (bug_id,) in self.cursor.fetchall()] |
1449 | - |
1450 | - def getDuplicates(self): |
1451 | - """Returns a list of (dupe_of, dupe) relations.""" |
1452 | - self.cursor.execute('SELECT dupe_of, dupe FROM duplicates ' |
1453 | - 'ORDER BY dupe, dupe_of') |
1454 | - return [(dupe_of, dupe) for (dupe_of, dupe) in self.cursor.fetchall()] |
1455 | - |
1456 | - |
1457 | -class Bug: |
1458 | - """Representation of a Bugzilla Bug""" |
1459 | - def __init__(self, backend, bug_id): |
1460 | - self.backend = backend |
1461 | - (self.bug_id, self.assigned_to, self.bug_file_loc, self.bug_severity, |
1462 | - self.bug_status, self.creation_ts, self.short_desc, self.op_sys, |
1463 | - self.priority, self.product, self.rep_platform, self.reporter, |
1464 | - self.version, self.component, self.resolution, |
1465 | - self.target_milestone, self.qa_contact, self.status_whiteboard, |
1466 | - self.keywords, self.alias) = backend.getBugInfo(bug_id) |
1467 | - |
1468 | - self._ccs = None |
1469 | - self._comments = None |
1470 | - self._attachments = None |
1471 | - |
1472 | - @property |
1473 | - def ccs(self): |
1474 | - """Return the IDs of people CC'd to this bug""" |
1475 | - if self._ccs is not None: |
1476 | - return self._ccs |
1477 | - self._ccs = self.backend.getBugCcs(self.bug_id) |
1478 | - return self._ccs |
1479 | - |
1480 | - @property |
1481 | - def comments(self): |
1482 | - """Return the comments attached to this bug""" |
1483 | - if self._comments is not None: |
1484 | - return self._comments |
1485 | - self._comments = self.backend.getBugComments(self.bug_id) |
1486 | - return self._comments |
1487 | - |
1488 | - @property |
1489 | - def attachments(self): |
1490 | - """Return the attachments for this bug""" |
1491 | - if self._attachments is not None: |
1492 | - return self._attachments |
1493 | - self._attachments = self.backend.getBugAttachments(self.bug_id) |
1494 | - return self._attachments |
1495 | - |
1496 | - def mapSeverity(self, bugtask): |
1497 | - """Set a Launchpad bug task's importance based on this bug's severity. |
1498 | - """ |
1499 | - bug_importer = getUtility(ILaunchpadCelebrities).bug_importer |
1500 | - importance_map = { |
1501 | - 'blocker': BugTaskImportance.CRITICAL, |
1502 | - 'critical': BugTaskImportance.CRITICAL, |
1503 | - 'major': BugTaskImportance.HIGH, |
1504 | - 'normal': BugTaskImportance.MEDIUM, |
1505 | - 'minor': BugTaskImportance.LOW, |
1506 | - 'trivial': BugTaskImportance.LOW, |
1507 | - 'enhancement': BugTaskImportance.WISHLIST |
1508 | - } |
1509 | - importance = importance_map.get( |
1510 | - self.bug_severity, BugTaskImportance.UNKNOWN) |
1511 | - bugtask.transitionToImportance(importance, bug_importer) |
1512 | - |
1513 | - def mapStatus(self, bugtask): |
1514 | - """Set a Launchpad bug task's status based on this bug's status. |
1515 | - |
1516 | - If the bug is in the RESOLVED, VERIFIED or CLOSED states, the |
1517 | - bug resolution is also taken into account when mapping the |
1518 | - status. |
1519 | - |
1520 | - Additional information about the bugzilla status is appended |
1521 | - to the bug task's status explanation. |
1522 | - """ |
1523 | - bug_importer = getUtility(ILaunchpadCelebrities).bug_importer |
1524 | - |
1525 | - if self.bug_status == 'ASSIGNED': |
1526 | - bugtask.transitionToStatus( |
1527 | - BugTaskStatus.CONFIRMED, bug_importer) |
1528 | - elif self.bug_status == 'NEEDINFO': |
1529 | - bugtask.transitionToStatus( |
1530 | - BugTaskStatus.INCOMPLETE, bug_importer) |
1531 | - elif self.bug_status == 'PENDINGUPLOAD': |
1532 | - bugtask.transitionToStatus( |
1533 | - BugTaskStatus.FIXCOMMITTED, bug_importer) |
1534 | - elif self.bug_status in ['RESOLVED', 'VERIFIED', 'CLOSED']: |
1535 | - # depends on the resolution: |
1536 | - if self.resolution == 'FIXED': |
1537 | - bugtask.transitionToStatus( |
1538 | - BugTaskStatus.FIXRELEASED, bug_importer) |
1539 | - else: |
1540 | - bugtask.transitionToStatus( |
1541 | - BugTaskStatus.INVALID, bug_importer) |
1542 | - else: |
1543 | - bugtask.transitionToStatus( |
1544 | - BugTaskStatus.NEW, bug_importer) |
1545 | - |
1546 | - # add the status to the notes section, to account for any lost |
1547 | - # information |
1548 | - bugzilla_status = 'Bugzilla status=%s' % self.bug_status |
1549 | - if self.resolution: |
1550 | - bugzilla_status += ' %s' % self.resolution |
1551 | - bugzilla_status += ', product=%s' % self.product |
1552 | - bugzilla_status += ', component=%s' % self.component |
1553 | - |
1554 | - if bugtask.statusexplanation: |
1555 | - bugtask.statusexplanation = '%s (%s)' % ( |
1556 | - bugtask.statusexplanation, bugzilla_status) |
1557 | - else: |
1558 | - bugtask.statusexplanation = bugzilla_status |
1559 | - |
1560 | - |
1561 | -class Bugzilla: |
1562 | - """Representation of a bugzilla instance""" |
1563 | - |
1564 | - def __init__(self, conn): |
1565 | - if conn is not None: |
1566 | - self.backend = BugzillaBackend(conn) |
1567 | - else: |
1568 | - self.backend = None |
1569 | - self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
1570 | - self.debian = getUtility(ILaunchpadCelebrities).debian |
1571 | - self.bugtracker = getUtility(ILaunchpadCelebrities).ubuntu_bugzilla |
1572 | - self.debbugs = getUtility(ILaunchpadCelebrities).debbugs |
1573 | - self.bugset = getUtility(IBugSet) |
1574 | - self.bugtaskset = getUtility(IBugTaskSet) |
1575 | - self.bugwatchset = getUtility(IBugWatchSet) |
1576 | - self.cveset = getUtility(ICveSet) |
1577 | - self.personset = getUtility(IPersonSet) |
1578 | - self.emailset = getUtility(IEmailAddressSet) |
1579 | - self.person_mapping = {} |
1580 | - |
1581 | - def person(self, bugzilla_id): |
1582 | - """Get the Launchpad person corresponding to the given Bugzilla ID""" |
1583 | - # Bugzilla treats a user ID of 0 as a NULL |
1584 | - if bugzilla_id == 0: |
1585 | - return None |
1586 | - |
1587 | - # Try and get the person using a cache of the mapping. We |
1588 | - # check to make sure the person still exists and has not been |
1589 | - # merged. |
1590 | - person = None |
1591 | - launchpad_id = self.person_mapping.get(bugzilla_id) |
1592 | - if launchpad_id is not None: |
1593 | - person = self.personset.get(launchpad_id) |
1594 | - if person is not None and person.merged is not None: |
1595 | - person = None |
1596 | - |
1597 | - # look up the person |
1598 | - if person is None: |
1599 | - email, displayname = self.backend.lookupUser(bugzilla_id) |
1600 | - |
1601 | - person = self.personset.ensurePerson( |
1602 | - email, displayname, PersonCreationRationale.BUGIMPORT, |
1603 | - comment=('when importing bugs from %s' |
1604 | - % self.bugtracker.baseurl)) |
1605 | - |
1606 | - # Bugzilla performs similar address checks to Launchpad, so |
1607 | - # if the Launchpad account has no preferred email, use the |
1608 | - # Bugzilla one. |
1609 | - emailaddr = self.emailset.getByEmail(email) |
1610 | - assert emailaddr is not None |
1611 | - if person.preferredemail != emailaddr: |
1612 | - person.validateAndEnsurePreferredEmail(emailaddr) |
1613 | - |
1614 | - self.person_mapping[bugzilla_id] = person.id |
1615 | - |
1616 | - return person |
1617 | - |
1618 | - def _getPackageName(self, bug): |
1619 | - """Returns the source package name for the given bug.""" |
1620 | - # we currently only support mapping Ubuntu bugs ... |
1621 | - if bug.product != 'Ubuntu': |
1622 | - raise AssertionError('product must be Ubuntu') |
1623 | - |
1624 | - # kernel bugs are currently filed against the "linux" |
1625 | - # component, which is not a source or binary package. The |
1626 | - # following mapping was provided by BenC: |
1627 | - if bug.component == 'linux': |
1628 | - cutoffdate = datetime.datetime(2004, 12, 1, |
1629 | - tzinfo=pytz.timezone('UTC')) |
1630 | - if bug.bug_status == 'NEEDINFO' and bug.creation_ts < cutoffdate: |
1631 | - pkgname = 'linux-source-2.6.12' |
1632 | - else: |
1633 | - pkgname = 'linux-source-2.6.15' |
1634 | - else: |
1635 | - pkgname = bug.component.encode('ASCII') |
1636 | - |
1637 | - try: |
1638 | - return self.ubuntu.guessPublishedSourcePackageName(pkgname) |
1639 | - except NotFoundError, e: |
1640 | - logger.warning('could not find package name for "%s": %s', |
1641 | - pkgname, str(e)) |
1642 | - return None |
1643 | - |
1644 | - def getLaunchpadBugTarget(self, bug): |
1645 | - """Returns a dictionary of arguments to createBug() that correspond |
1646 | - to the given bugzilla bug. |
1647 | - """ |
1648 | - srcpkg = self._getPackageName(bug) |
1649 | - return { |
1650 | - 'distribution': self.ubuntu, |
1651 | - 'sourcepackagename': srcpkg, |
1652 | - } |
1653 | - |
1654 | - def getLaunchpadMilestone(self, bug): |
1655 | - """Return the Launchpad milestone for a Bugzilla bug. |
1656 | - |
1657 | - If the milestone does not exist, then it is created. |
1658 | - """ |
1659 | - if bug.product != 'Ubuntu': |
1660 | - raise AssertionError('product must be Ubuntu') |
1661 | - |
1662 | - # Bugzilla uses a value of "---" to represent "no selected Milestone" |
1663 | - # Launchpad represents this by setting the milestone column to NULL. |
1664 | - if bug.target_milestone is None or bug.target_milestone == '---': |
1665 | - return None |
1666 | - |
1667 | - # generate a Launchpad name from the Milestone name: |
1668 | - name = re.sub(r'[^a-z0-9\+\.\-]', '-', bug.target_milestone.lower()) |
1669 | - |
1670 | - milestone = self.ubuntu.getMilestone(name) |
1671 | - if milestone is None: |
1672 | - milestone = self.ubuntu.currentseries.newMilestone(name) |
1673 | - Store.of(milestone).flush() |
1674 | - return milestone |
1675 | - |
1676 | - def getLaunchpadUpstreamProduct(self, bug): |
1677 | - """Find the upstream product for the given Bugzilla bug. |
1678 | - |
1679 | - This function relies on the package -> product linkage having been |
1680 | - entered in advance. |
1681 | - """ |
1682 | - srcpkgname = self._getPackageName(bug) |
1683 | - # find a product series |
1684 | - series = None |
1685 | - for series in self.ubuntu.series: |
1686 | - srcpkg = series.getSourcePackage(srcpkgname) |
1687 | - if srcpkg: |
1688 | - series = srcpkg.productseries |
1689 | - if series: |
1690 | - return series.product |
1691 | - else: |
1692 | - logger.warning('could not find upstream product for ' |
1693 | - 'source package "%s"', srcpkgname.name) |
1694 | - return None |
1695 | - |
1696 | - _bug_re = re.compile('bug\s*#?\s*(?P<id>\d+)', re.IGNORECASE) |
1697 | - |
1698 | - def replaceBugRef(self, match): |
1699 | - # XXX: jamesh 2005-10-24: |
1700 | - # this is where bug number rewriting would be plugged in |
1701 | - bug_id = int(match.group('id')) |
1702 | - url = '%s/%d' % (canonical_url(self.bugtracker), bug_id) |
1703 | - return '%s [%s]' % (match.group(0), url) |
1704 | - |
1705 | - def handleBug(self, bug_id): |
1706 | - """Maybe import a single bug. |
1707 | - |
1708 | - If the bug has already been imported (detected by checking for |
1709 | - a bug watch), it is skipped. |
1710 | - """ |
1711 | - logger.info('Handling Bugzilla bug %d', bug_id) |
1712 | - |
1713 | - # is there a bug watch on the bug? |
1714 | - lp_bug = self.bugset.queryByRemoteBug(self.bugtracker, bug_id) |
1715 | - |
1716 | - # if we already have an associated bug, don't add a new one. |
1717 | - if lp_bug is not None: |
1718 | - logger.info('Bugzilla bug %d is already being watched by ' |
1719 | - 'Launchpad bug %d', bug_id, lp_bug.id) |
1720 | - return lp_bug |
1721 | - |
1722 | - bug = Bug(self.backend, bug_id) |
1723 | - |
1724 | - comments = bug.comments[:] |
1725 | - |
1726 | - # create a message for the initial comment: |
1727 | - msgset = getUtility(IMessageSet) |
1728 | - who, when, text = comments.pop(0) |
1729 | - text = self._bug_re.sub(self.replaceBugRef, text) |
1730 | - # If a URL is associated with the bug, add it to the description: |
1731 | - if bug.bug_file_loc: |
1732 | - text = text + '\n\n' + bug.bug_file_loc |
1733 | - # the initial comment can't be empty: |
1734 | - if not text.strip(): |
1735 | - text = '<empty comment>' |
1736 | - msg = msgset.fromText(bug.short_desc, text, self.person(who), when) |
1737 | - |
1738 | - # create the bug |
1739 | - target = self.getLaunchpadBugTarget(bug) |
1740 | - params = CreateBugParams( |
1741 | - msg=msg, datecreated=bug.creation_ts, title=bug.short_desc, |
1742 | - owner=self.person(bug.reporter)) |
1743 | - params.setBugTarget(**target) |
1744 | - lp_bug = self.bugset.createBug(params) |
1745 | - |
1746 | - # add the bug watch: |
1747 | - lp_bug.addWatch(self.bugtracker, str(bug.bug_id), lp_bug.owner) |
1748 | - |
1749 | - # add remaining comments, and add CVEs found in all text |
1750 | - lp_bug.findCvesInText(text, lp_bug.owner) |
1751 | - for (who, when, text) in comments: |
1752 | - text = self._bug_re.sub(self.replaceBugRef, text) |
1753 | - msg = msgset.fromText(msg.followup_title, text, |
1754 | - self.person(who), when) |
1755 | - lp_bug.linkMessage(msg) |
1756 | - |
1757 | - # subscribe QA contact and CC's |
1758 | - if bug.qa_contact: |
1759 | - lp_bug.subscribe( |
1760 | - self.person(bug.qa_contact), self.person(bug.reporter)) |
1761 | - for cc in bug.ccs: |
1762 | - lp_bug.subscribe(self.person(cc), self.person(bug.reporter)) |
1763 | - |
1764 | - # translate bugzilla status and severity to LP equivalents |
1765 | - task = lp_bug.bugtasks[0] |
1766 | - task.datecreated = bug.creation_ts |
1767 | - task.transitionToAssignee(self.person(bug.assigned_to)) |
1768 | - task.statusexplanation = bug.status_whiteboard |
1769 | - bug.mapSeverity(task) |
1770 | - bug.mapStatus(task) |
1771 | - |
1772 | - # bugs with an alias of the form "deb1234" have been imported |
1773 | - # from the Debian bug tracker by the "debzilla" program. For |
1774 | - # these bugs, generate a task and watch on the corresponding |
1775 | - # bugs.debian.org bug. |
1776 | - if bug.alias: |
1777 | - if re.match(r'^deb\d+$', bug.alias): |
1778 | - watch = self.bugwatchset.createBugWatch( |
1779 | - lp_bug, lp_bug.owner, self.debbugs, bug.alias[3:]) |
1780 | - debtarget = self.debian |
1781 | - if target['sourcepackagename']: |
1782 | - debtarget = debtarget.getSourcePackage( |
1783 | - target['sourcepackagename']) |
1784 | - debtask = self.bugtaskset.createTask( |
1785 | - lp_bug, lp_bug.owner, debtarget) |
1786 | - debtask.datecreated = bug.creation_ts |
1787 | - debtask.bugwatch = watch |
1788 | - else: |
1789 | - # generate a Launchpad name from the alias: |
1790 | - name = re.sub(r'[^a-z0-9\+\.\-]', '-', bug.alias.lower()) |
1791 | - lp_bug.name = name |
1792 | - |
1793 | - # for UPSTREAM bugs, try to find whether the URL field contains |
1794 | - # a bug reference. |
1795 | - if bug.bug_status == 'UPSTREAM': |
1796 | - # see if the URL field contains a bug tracker reference |
1797 | - watches = self.bugwatchset.fromText(bug.bug_file_loc, |
1798 | - lp_bug, lp_bug.owner) |
1799 | - # find the upstream product for this bug |
1800 | - product = self.getLaunchpadUpstreamProduct(bug) |
1801 | - |
1802 | - # if we created a watch, and there is an upstream product, |
1803 | - # create a new task and link it to the watch. |
1804 | - if len(watches) > 0: |
1805 | - if product: |
1806 | - upstreamtask = self.bugtaskset.createTask( |
1807 | - lp_bug, lp_bug.owner, product) |
1808 | - upstreamtask.datecreated = bug.creation_ts |
1809 | - upstreamtask.bugwatch = watches[0] |
1810 | - else: |
1811 | - logger.warning('Could not find upstream product to link ' |
1812 | - 'bug %d to', lp_bug.id) |
1813 | - |
1814 | - # translate milestone linkage |
1815 | - task.milestone = self.getLaunchpadMilestone(bug) |
1816 | - |
1817 | - # import attachments |
1818 | - for (attach_id, creation_ts, description, mimetype, ispatch, |
1819 | - filename, thedata, submitter_id) in bug.attachments: |
1820 | - # if the filename is missing for some reason, use a generic one. |
1821 | - if filename is None or filename.strip() == '': |
1822 | - filename = 'untitled' |
1823 | - logger.debug('Creating attachment %s for bug %d', |
1824 | - filename, bug.bug_id) |
1825 | - if ispatch: |
1826 | - attach_type = BugAttachmentType.PATCH |
1827 | - mimetype = 'text/plain' |
1828 | - else: |
1829 | - attach_type = BugAttachmentType.UNSPECIFIED |
1830 | - |
1831 | - # look for a message starting with "Created an attachment (id=NN)" |
1832 | - for msg in lp_bug.messages: |
1833 | - if msg.text_contents.startswith( |
1834 | - 'Created an attachment (id=%d)' % attach_id): |
1835 | - break |
1836 | - else: |
1837 | - # could not find the add message, so create one: |
1838 | - msg = msgset.fromText(description, |
1839 | - 'Created attachment %s' % filename, |
1840 | - self.person(submitter_id), |
1841 | - creation_ts) |
1842 | - lp_bug.linkMessage(msg) |
1843 | - |
1844 | - filealias = getUtility(ILibraryFileAliasSet).create( |
1845 | - name=filename, |
1846 | - size=len(thedata), |
1847 | - file=StringIO(thedata), |
1848 | - contentType=mimetype) |
1849 | - |
1850 | - getUtility(IBugAttachmentSet).create( |
1851 | - bug=lp_bug, filealias=filealias, attach_type=attach_type, |
1852 | - title=description, message=msg) |
1853 | - |
1854 | - return lp_bug |
1855 | - |
1856 | - def processDuplicates(self, trans): |
1857 | - """Mark Launchpad bugs as duplicates based on Bugzilla duplicates. |
1858 | - |
1859 | - Launchpad bug A will be marked as a duplicate of bug B if: |
1860 | - * bug A watches bugzilla bug A' |
1861 | - * bug B watches bugzilla bug B' |
1862 | - * bug A' is a duplicate of bug B' |
1863 | - * bug A is not currently a duplicate of any other bug. |
1864 | - """ |
1865 | - |
1866 | - logger.info('Processing duplicate bugs') |
1867 | - bugmap = {} |
1868 | - |
1869 | - def getlpbug(bugid): |
1870 | - """Get the Launchpad bug corresponding to the given remote ID |
1871 | - |
1872 | - This function makes use of a cache dictionary to reduce the |
1873 | - number of lookups. |
1874 | - """ |
1875 | - lpbugid = bugmap.get(bugid) |
1876 | - if lpbugid is not None: |
1877 | - if lpbugid != 0: |
1878 | - lpbug = self.bugset.get(lpbugid) |
1879 | - else: |
1880 | - lpbug = None |
1881 | - else: |
1882 | - lpbug = self.bugset.queryByRemoteBug(self.bugtracker, bugid) |
1883 | - if lpbug is not None: |
1884 | - bugmap[bugid] = lpbug.id |
1885 | - else: |
1886 | - bugmap[bugid] = 0 |
1887 | - return lpbug |
1888 | - |
1889 | - for (dupe_of, dupe) in self.backend.getDuplicates(): |
1890 | - # get the Launchpad bugs corresponding to the two Bugzilla bugs: |
1891 | - trans.begin() |
1892 | - lpdupe_of = getlpbug(dupe_of) |
1893 | - lpdupe = getlpbug(dupe) |
1894 | - # if both bugs exist in Launchpad, and lpdupe is not already |
1895 | - # a duplicate, mark it as a duplicate of lpdupe_of. |
1896 | - if (lpdupe_of is not None and lpdupe is not None and |
1897 | - lpdupe.duplicateof is None): |
1898 | - logger.info('Marking %d as a duplicate of %d', |
1899 | - lpdupe.id, lpdupe_of.id) |
1900 | - lpdupe.markAsDuplicate(lpdupe_of) |
1901 | - trans.commit() |
1902 | - |
1903 | - def importBugs(self, trans, product=None, component=None, status=None): |
1904 | - """Import Bugzilla bugs matching the given constraints. |
1905 | - |
1906 | - Each of product, component and status gives a list of |
1907 | - products, components or statuses to limit the import to. An |
1908 | - empty list matches all products, components or statuses. |
1909 | - """ |
1910 | - if product is None: |
1911 | - product = [] |
1912 | - if component is None: |
1913 | - component = [] |
1914 | - if status is None: |
1915 | - status = [] |
1916 | - |
1917 | - bugs = self.backend.findBugs(product=product, |
1918 | - component=component, |
1919 | - status=status) |
1920 | - for bug_id in bugs: |
1921 | - trans.begin() |
1922 | - try: |
1923 | - self.handleBug(bug_id) |
1924 | - except (SystemExit, KeyboardInterrupt): |
1925 | - raise |
1926 | - except: |
1927 | - logger.exception('Could not import Bugzilla bug #%d', bug_id) |
1928 | - trans.abort() |
1929 | - else: |
1930 | - trans.commit() |
1931 | |
1932 | === added file 'lib/lp/bugs/templates/bugtarget-tasks-and-nominations-table-row.pt' |
1933 | --- lib/lp/bugs/templates/bugtarget-tasks-and-nominations-table-row.pt 1970-01-01 00:00:00 +0000 |
1934 | +++ lib/lp/bugs/templates/bugtarget-tasks-and-nominations-table-row.pt 2011-08-03 17:36:12 +0000 |
1935 | @@ -0,0 +1,11 @@ |
1936 | +<tal:bugtarget xmlns:tal="http://xml.zope.org/namespaces/tal"> |
1937 | + <tr style="opacity: 0.5"> |
1938 | + <td></td> |
1939 | + <td> |
1940 | + <a tal:attributes="href context/fmt:url; |
1941 | + class context/image:sprite_css" |
1942 | + tal:content="context/bugtargetdisplayname" /> |
1943 | + </td> |
1944 | + <td colspan="5"></td> |
1945 | + </tr> |
1946 | +</tal:bugtarget> |
1947 | |
1948 | === modified file 'lib/lp/bugs/tests/test_doc.py' |
1949 | --- lib/lp/bugs/tests/test_doc.py 2010-10-06 18:53:53 +0000 |
1950 | +++ lib/lp/bugs/tests/test_doc.py 2011-08-03 17:36:12 +0000 |
1951 | @@ -158,12 +158,6 @@ |
1952 | layer=LaunchpadZopelessLayer, |
1953 | setUp=bugNotificationSendingSetUp, |
1954 | tearDown=bugNotificationSendingTearDown), |
1955 | - 'bugzilla-import.txt': LayeredDocFileSuite( |
1956 | - '../doc/bugzilla-import.txt', |
1957 | - setUp=setUp, tearDown=tearDown, |
1958 | - stdout_logging_level=logging.WARNING, |
1959 | - layer=LaunchpadZopelessLayer |
1960 | - ), |
1961 | 'bug-export.txt': LayeredDocFileSuite( |
1962 | '../doc/bug-export.txt', |
1963 | setUp=setUp, tearDown=tearDown, |
1964 | |
1965 | === modified file 'lib/lp/code/configure.zcml' |
1966 | --- lib/lp/code/configure.zcml 2011-06-20 18:58:27 +0000 |
1967 | +++ lib/lp/code/configure.zcml 2011-08-03 17:36:12 +0000 |
1968 | @@ -715,6 +715,7 @@ |
1969 | product |
1970 | series |
1971 | review_status |
1972 | + failure_bug |
1973 | rcs_type |
1974 | cvs_root |
1975 | cvs_module |
1976 | @@ -733,7 +734,8 @@ |
1977 | requestImport"/> |
1978 | <require |
1979 | permission="launchpad.Edit" |
1980 | - attributes="updateFromData"/> |
1981 | + attributes="updateFromData |
1982 | + linkFailureBug"/> |
1983 | |
1984 | <!-- ICodeImport has no set_schema, because all modifications should be |
1985 | done through methods that create CodeImportEvent objects when |
1986 | |
1987 | === modified file 'lib/lp/code/interfaces/codeimport.py' |
1988 | --- lib/lp/code/interfaces/codeimport.py 2011-02-23 20:26:53 +0000 |
1989 | +++ lib/lp/code/interfaces/codeimport.py 2011-08-03 17:36:12 +0000 |
1990 | @@ -40,6 +40,7 @@ |
1991 | |
1992 | from canonical.launchpad import _ |
1993 | from lp.app.validators import LaunchpadValidationError |
1994 | +from lp.bugs.interfaces.bug import IBug |
1995 | from lp.code.enums import ( |
1996 | CodeImportReviewStatus, |
1997 | RevisionControlSystems, |
1998 | @@ -100,6 +101,11 @@ |
1999 | description=_("The Bazaar branch produced by the " |
2000 | "import system."))) |
2001 | |
2002 | + failure_bug = ReferenceChoice( |
2003 | + title=_('Failure bug'), required=False, readonly=False, |
2004 | + vocabulary='Bug', schema=IBug, |
2005 | + description=_("The bug that is causing this import to fail.")) |
2006 | + |
2007 | registrant = PublicPersonChoice( |
2008 | title=_('Registrant'), required=True, readonly=True, |
2009 | vocabulary='ValidPersonOrTeam', |
2010 | @@ -188,6 +194,14 @@ |
2011 | None if no changes were made. |
2012 | """ |
2013 | |
2014 | + def linkFailureBug(bug): |
2015 | + """Link the bug that causes this import to fail. |
2016 | + |
2017 | + This method requires the review_status to be FAILING. |
2018 | + |
2019 | + :param bug: The bug that is causing the import to fail. |
2020 | + """ |
2021 | + |
2022 | def tryFailingImportAgain(user): |
2023 | """Try a failing import again. |
2024 | |
2025 | |
2026 | === modified file 'lib/lp/code/model/codeimport.py' |
2027 | --- lib/lp/code/model/codeimport.py 2011-04-27 01:42:46 +0000 |
2028 | +++ lib/lp/code/model/codeimport.py 2011-08-03 17:36:12 +0000 |
2029 | @@ -85,6 +85,9 @@ |
2030 | dbName='assignee', foreignKey='Person', |
2031 | storm_validator=validate_public_person, notNull=False, default=None) |
2032 | |
2033 | + failure_bug = ForeignKey( |
2034 | + dbName='failure_bug', foreignKey='Bug', notNull=False, default=None) |
2035 | + |
2036 | review_status = EnumCol(schema=CodeImportReviewStatus, notNull=True, |
2037 | default=CodeImportReviewStatus.NEW) |
2038 | |
2039 | @@ -190,6 +193,8 @@ |
2040 | else: |
2041 | new_whiteboard = whiteboard |
2042 | self.branch.whiteboard = whiteboard |
2043 | + if data.get('review_status', None) != CodeImportReviewStatus.FAILING: |
2044 | + self.failure_bug = None |
2045 | token = event_set.beginModify(self) |
2046 | for name, value in data.items(): |
2047 | setattr(self, name, value) |
2048 | @@ -206,6 +211,13 @@ |
2049 | def __repr__(self): |
2050 | return "<CodeImport for %s>" % self.branch.unique_name |
2051 | |
2052 | + def linkFailureBug(self, bug): |
2053 | + """See `ICodeImport`.""" |
2054 | + if self.review_status != CodeImportReviewStatus.FAILING: |
2055 | + raise AssertionError( |
2056 | + "review_status is %s not FAILING" % self.review_status.name) |
2057 | + self.failure_bug = bug |
2058 | + |
2059 | def tryFailingImportAgain(self, user): |
2060 | """See `ICodeImport`.""" |
2061 | if self.review_status != CodeImportReviewStatus.FAILING: |
2062 | |
2063 | === modified file 'lib/lp/code/model/tests/test_codeimport.py' |
2064 | --- lib/lp/code/model/tests/test_codeimport.py 2011-05-27 21:12:25 +0000 |
2065 | +++ lib/lp/code/model/tests/test_codeimport.py 2011-08-03 17:36:12 +0000 |
2066 | @@ -391,6 +391,31 @@ |
2067 | self.assertEqual( |
2068 | CodeImportReviewStatus.FAILING, code_import.review_status) |
2069 | |
2070 | + def test_mark_failing_with_bug(self): |
2071 | + # Marking an import as failing and linking to a bug. |
2072 | + code_import = self.factory.makeCodeImport() |
2073 | + code_import.updateFromData( |
2074 | + {'review_status':CodeImportReviewStatus.FAILING}, |
2075 | + self.import_operator) |
2076 | + self.assertEquals(None, code_import.failure_bug) |
2077 | + bug = self.factory.makeBug() |
2078 | + code_import.linkFailureBug(bug) |
2079 | + self.assertEqual( |
2080 | + CodeImportReviewStatus.FAILING, code_import.review_status) |
2081 | + self.assertEquals(bug, code_import.failure_bug) |
2082 | + |
2083 | + def test_mark_no_longer_failing_with_bug(self): |
2084 | + # Marking an import as no longer failing removes the failure bug link. |
2085 | + code_import = self.factory.makeCodeImport() |
2086 | + code_import.updateFromData( |
2087 | + {'review_status':CodeImportReviewStatus.FAILING}, |
2088 | + self.import_operator) |
2089 | + code_import.linkFailureBug(self.factory.makeBug()) |
2090 | + code_import.updateFromData( |
2091 | + {'review_status':CodeImportReviewStatus.REVIEWED}, |
2092 | + self.import_operator) |
2093 | + self.assertEquals(None, code_import.failure_bug) |
2094 | + |
2095 | |
2096 | class TestCodeImportResultsAttribute(TestCaseWithFactory): |
2097 | """Test the results attribute of a CodeImport.""" |
2098 | |
2099 | === modified file 'lib/lp/codehosting/codeimport/tests/test_uifactory.py' |
2100 | --- lib/lp/codehosting/codeimport/tests/test_uifactory.py 2011-07-19 18:09:01 +0000 |
2101 | +++ lib/lp/codehosting/codeimport/tests/test_uifactory.py 2011-08-03 17:36:12 +0000 |
2102 | @@ -10,6 +10,7 @@ |
2103 | import unittest |
2104 | |
2105 | from lp.codehosting.codeimport.uifactory import LoggingUIFactory |
2106 | +from lp.services.log.logger import BufferLogger |
2107 | from lp.testing import ( |
2108 | FakeTime, |
2109 | TestCase, |
2110 | @@ -22,19 +23,19 @@ |
2111 | def setUp(self): |
2112 | TestCase.setUp(self) |
2113 | self.fake_time = FakeTime(12345) |
2114 | - self.messages = [] |
2115 | + self.logger = BufferLogger() |
2116 | |
2117 | def makeLoggingUIFactory(self): |
2118 | """Make a `LoggingUIFactory` with fake time and contained output.""" |
2119 | return LoggingUIFactory( |
2120 | - time_source=self.fake_time.now, writer=self.messages.append) |
2121 | + time_source=self.fake_time.now, logger=self.logger) |
2122 | |
2123 | def test_first_progress_updates(self): |
2124 | # The first call to progress generates some output. |
2125 | factory = self.makeLoggingUIFactory() |
2126 | bar = factory.nested_progress_bar() |
2127 | bar.update("hi") |
2128 | - self.assertEqual(['hi'], self.messages) |
2129 | + self.assertEqual('INFO hi\n', self.logger.getLogBuffer()) |
2130 | |
2131 | def test_second_rapid_progress_doesnt_update(self): |
2132 | # The second of two progress calls that are less than the factory's |
2133 | @@ -44,7 +45,7 @@ |
2134 | bar.update("hi") |
2135 | self.fake_time.advance(factory.interval / 2) |
2136 | bar.update("there") |
2137 | - self.assertEqual(['hi'], self.messages) |
2138 | + self.assertEqual('INFO hi\n', self.logger.getLogBuffer()) |
2139 | |
2140 | def test_second_slow_progress_updates(self): |
2141 | # The second of two progress calls that are more than the factory's |
2142 | @@ -54,7 +55,10 @@ |
2143 | bar.update("hi") |
2144 | self.fake_time.advance(factory.interval * 2) |
2145 | bar.update("there") |
2146 | - self.assertEqual(['hi', 'there'], self.messages) |
2147 | + self.assertEqual( |
2148 | + 'INFO hi\n' |
2149 | + 'INFO there\n', |
2150 | + self.logger.getLogBuffer()) |
2151 | |
2152 | def test_first_progress_on_new_bar_updates(self): |
2153 | # The first progress on a new progress task always generates output. |
2154 | @@ -64,14 +68,15 @@ |
2155 | self.fake_time.advance(factory.interval / 2) |
2156 | bar2 = factory.nested_progress_bar() |
2157 | bar2.update("there") |
2158 | - self.assertEqual(['hi', 'hi:there'], self.messages) |
2159 | + self.assertEqual( |
2160 | + 'INFO hi\nINFO hi:there\n', self.logger.getLogBuffer()) |
2161 | |
2162 | def test_update_with_count_formats_nicely(self): |
2163 | # When more details are passed to update, they are formatted nicely. |
2164 | factory = self.makeLoggingUIFactory() |
2165 | bar = factory.nested_progress_bar() |
2166 | bar.update("hi", 1, 8) |
2167 | - self.assertEqual(['hi 1/8'], self.messages) |
2168 | + self.assertEqual('INFO hi 1/8\n', self.logger.getLogBuffer()) |
2169 | |
2170 | def test_report_transport_activity_reports_bytes_since_last_update(self): |
2171 | # If there is no call to _progress_updated for 'interval' seconds, the |
2172 | @@ -94,9 +99,54 @@ |
2173 | # activity info. |
2174 | bar.update("hi", 3, 10) |
2175 | self.assertEqual( |
2176 | - ['hi 1/10', 'hi 2/10', '110 bytes transferred | hi 2/10', |
2177 | - 'hi 3/10'], |
2178 | - self.messages) |
2179 | + 'INFO hi 1/10\n' |
2180 | + 'INFO hi 2/10\n' |
2181 | + 'INFO 110 bytes transferred | hi 2/10\n' |
2182 | + 'INFO hi 3/10\n', |
2183 | + self.logger.getLogBuffer()) |
2184 | + |
2185 | + def test_note(self): |
2186 | + factory = self.makeLoggingUIFactory() |
2187 | + factory.note("Banja Luka") |
2188 | + self.assertEqual('INFO Banja Luka\n', self.logger.getLogBuffer()) |
2189 | + |
2190 | + def test_show_error(self): |
2191 | + factory = self.makeLoggingUIFactory() |
2192 | + factory.show_error("Exploding Peaches") |
2193 | + self.assertEqual( |
2194 | + "ERROR Exploding Peaches\n", self.logger.getLogBuffer()) |
2195 | + |
2196 | + def test_confirm_action(self): |
2197 | + factory = self.makeLoggingUIFactory() |
2198 | + self.assertTrue(factory.confirm_action( |
2199 | + "How are you %(when)s?", "wellness", {"when": "today"})) |
2200 | + |
2201 | + def test_show_message(self): |
2202 | + factory = self.makeLoggingUIFactory() |
2203 | + factory.show_message("Peaches") |
2204 | + self.assertEqual("INFO Peaches\n", self.logger.getLogBuffer()) |
2205 | + |
2206 | + def test_get_username(self): |
2207 | + factory = self.makeLoggingUIFactory() |
2208 | + self.assertIs( |
2209 | + None, factory.get_username("Who are you %(when)s?", when="today")) |
2210 | + |
2211 | + def test_get_password(self): |
2212 | + factory = self.makeLoggingUIFactory() |
2213 | + self.assertIs( |
2214 | + None, |
2215 | + factory.get_password("How is your %(drink)s", drink="coffee")) |
2216 | + |
2217 | + def test_show_warning(self): |
2218 | + factory = self.makeLoggingUIFactory() |
2219 | + factory.show_warning("Peaches") |
2220 | + self.assertEqual("WARNING Peaches\n", self.logger.getLogBuffer()) |
2221 | + |
2222 | + def test_show_warning_unicode(self): |
2223 | + factory = self.makeLoggingUIFactory() |
2224 | + factory.show_warning(u"Peach\xeas") |
2225 | + self.assertEqual( |
2226 | + "WARNING Peach\xc3\xaas\n", self.logger.getLogBuffer()) |
2227 | |
2228 | def test_user_warning(self): |
2229 | factory = self.makeLoggingUIFactory() |
2230 | @@ -106,9 +156,13 @@ |
2231 | "from_format": "athing", |
2232 | "to_format": "anotherthing", |
2233 | } |
2234 | - self.assertEqual([message], self.messages) |
2235 | + self.assertEqual("WARNING %s\n" % message, self.logger.getLogBuffer()) |
2236 | + |
2237 | + def test_clear_term(self): |
2238 | + factory = self.makeLoggingUIFactory() |
2239 | + factory.clear_term() |
2240 | + self.assertEqual("", self.logger.getLogBuffer()) |
2241 | |
2242 | |
2243 | def test_suite(): |
2244 | return unittest.TestLoader().loadTestsFromName(__name__) |
2245 | - |
2246 | |
2247 | === modified file 'lib/lp/codehosting/codeimport/uifactory.py' |
2248 | --- lib/lp/codehosting/codeimport/uifactory.py 2011-07-19 18:09:01 +0000 |
2249 | +++ lib/lp/codehosting/codeimport/uifactory.py 2011-08-03 17:36:12 +0000 |
2250 | @@ -10,38 +10,80 @@ |
2251 | import sys |
2252 | import time |
2253 | |
2254 | +from bzrlib.ui import NoninteractiveUIFactory |
2255 | from bzrlib.ui.text import ( |
2256 | TextProgressView, |
2257 | - TextUIFactory, |
2258 | ) |
2259 | |
2260 | |
2261 | -class LoggingUIFactory(TextUIFactory): |
2262 | +class LoggingUIFactory(NoninteractiveUIFactory): |
2263 | """A UI Factory that produces reasonably sparse logging style output. |
2264 | |
2265 | The goal is to produce a line of output no more often than once a minute |
2266 | (by default). |
2267 | """ |
2268 | |
2269 | - def __init__(self, time_source=time.time, writer=None, interval=60.0): |
2270 | + # XXX: JelmerVernooij 2011-08-02 bug=820127: This seems generic enough to |
2271 | + # live in bzrlib.ui |
2272 | + |
2273 | + def __init__(self, time_source=time.time, logger=None, interval=60.0): |
2274 | """Construct a `LoggingUIFactory`. |
2275 | |
2276 | :param time_source: A callable that returns time in seconds since the |
2277 | epoch. Defaults to ``time.time`` and should be replaced with |
2278 | something deterministic in tests. |
2279 | - :param writer: A callable that takes a string and displays it. It is |
2280 | - not called with newline terminated strings. |
2281 | + :param logger: The logger object to write to |
2282 | :param interval: Don't produce output more often than once every this |
2283 | many seconds. Defaults to 60 seconds. |
2284 | """ |
2285 | - TextUIFactory.__init__(self) |
2286 | + NoninteractiveUIFactory.__init__(self) |
2287 | self.interval = interval |
2288 | - self.writer = writer |
2289 | + self.logger = logger |
2290 | self._progress_view = LoggingTextProgressView( |
2291 | - time_source, writer, interval) |
2292 | + time_source, lambda m: logger.info("%s", m), interval) |
2293 | |
2294 | def show_user_warning(self, warning_id, **message_args): |
2295 | - self.writer(self.format_user_warning(warning_id, message_args)) |
2296 | + self.logger.warning( |
2297 | + "%s", self.format_user_warning(warning_id, message_args)) |
2298 | + |
2299 | + def show_warning(self, msg): |
2300 | + if isinstance(msg, unicode): |
2301 | + msg = msg.encode("utf-8") |
2302 | + self.logger.warning("%s", msg) |
2303 | + |
2304 | + def get_username(self, prompt, **kwargs): |
2305 | + return None |
2306 | + |
2307 | + def get_password(self, prompt, **kwargs): |
2308 | + return None |
2309 | + |
2310 | + def show_message(self, msg): |
2311 | + self.logger.info("%s", msg) |
2312 | + |
2313 | + def note(self, msg): |
2314 | + self.logger.info("%s", msg) |
2315 | + |
2316 | + def show_error(self, msg): |
2317 | + self.logger.error("%s", msg) |
2318 | + |
2319 | + def _progress_updated(self, task): |
2320 | + """A task has been updated and wants to be displayed. |
2321 | + """ |
2322 | + if not self._task_stack: |
2323 | + self.logger.warning("%r updated but no tasks are active", task) |
2324 | + self._progress_view.show_progress(task) |
2325 | + |
2326 | + def _progress_all_finished(self): |
2327 | + self._progress_view.clear() |
2328 | + |
2329 | + def report_transport_activity(self, transport, byte_count, direction): |
2330 | + """Called by transports as they do IO. |
2331 | + |
2332 | + This may update a progress bar, spinner, or similar display. |
2333 | + By default it does nothing. |
2334 | + """ |
2335 | + self._progress_view.show_transport_activity(transport, |
2336 | + direction, byte_count) |
2337 | |
2338 | |
2339 | class LoggingTextProgressView(TextProgressView): |
2340 | |
2341 | === modified file 'lib/lp/codehosting/codeimport/worker.py' |
2342 | --- lib/lp/codehosting/codeimport/worker.py 2011-07-20 17:20:03 +0000 |
2343 | +++ lib/lp/codehosting/codeimport/worker.py 2011-08-03 17:36:12 +0000 |
2344 | @@ -601,8 +601,7 @@ |
2345 | def _doImport(self): |
2346 | self._logger.info("Starting job.") |
2347 | saved_factory = bzrlib.ui.ui_factory |
2348 | - bzrlib.ui.ui_factory = LoggingUIFactory( |
2349 | - writer=lambda m: self._logger.info('%s', m)) |
2350 | + bzrlib.ui.ui_factory = LoggingUIFactory(logger=self._logger) |
2351 | try: |
2352 | self._logger.info( |
2353 | "Getting exising bzr branch from central store.") |
2354 | @@ -635,9 +634,11 @@ |
2355 | except Exception, e: |
2356 | if e.__class__ in self.unsupported_feature_exceptions: |
2357 | self._logger.info( |
2358 | - "Unable to import branch because of limitations in Bazaar.") |
2359 | + "Unable to import branch because of limitations in " |
2360 | + "Bazaar.") |
2361 | self._logger.info(str(e)) |
2362 | - return CodeImportWorkerExitCode.FAILURE_UNSUPPORTED_FEATURE |
2363 | + return ( |
2364 | + CodeImportWorkerExitCode.FAILURE_UNSUPPORTED_FEATURE) |
2365 | elif e.__class__ in self.invalid_branch_exceptions: |
2366 | self._logger.info("Branch invalid: %s", e(str)) |
2367 | return CodeImportWorkerExitCode.FAILURE_INVALID |
2368 | |
2369 | === modified file 'lib/lp/registry/browser/distributionsourcepackage.py' |
2370 | --- lib/lp/registry/browser/distributionsourcepackage.py 2011-07-28 17:34:34 +0000 |
2371 | +++ lib/lp/registry/browser/distributionsourcepackage.py 2011-08-03 17:36:12 +0000 |
2372 | @@ -21,6 +21,7 @@ |
2373 | import operator |
2374 | |
2375 | from lazr.delegates import delegates |
2376 | +from lazr.restful.interfaces import IJSONRequestCache |
2377 | import pytz |
2378 | from zope.component import ( |
2379 | adapter, |
2380 | @@ -337,6 +338,11 @@ |
2381 | expose_structural_subscription_data_to_js( |
2382 | self.context, self.request, self.user) |
2383 | |
2384 | + pub = self.context.latest_overall_publication |
2385 | + if pub: |
2386 | + IJSONRequestCache(self.request).objects['archive_context_url'] = ( |
2387 | + canonical_url(pub.archive, path_only_if_possible=True)) |
2388 | + |
2389 | @property |
2390 | def label(self): |
2391 | return self.context.title |
2392 | |
2393 | === modified file 'lib/lp/registry/templates/distributionsourcepackage-index.pt' |
2394 | --- lib/lp/registry/templates/distributionsourcepackage-index.pt 2011-07-15 11:18:47 +0000 |
2395 | +++ lib/lp/registry/templates/distributionsourcepackage-index.pt 2011-08-03 17:36:12 +0000 |
2396 | @@ -188,8 +188,6 @@ |
2397 | |
2398 | </tal:rows> |
2399 | </table> |
2400 | - <script |
2401 | - tal:content="string:LP.cache['archive_context_url'] = '${archive/fmt:url}';"></script> |
2402 | <metal:js use-macro="archive/@@+macros/expandable-table-js"/> |
2403 | |
2404 | </div> |
2405 | |
2406 | === modified file 'lib/lp/services/scripts/tests/__init__.py' |
2407 | --- lib/lp/services/scripts/tests/__init__.py 2010-11-16 12:56:01 +0000 |
2408 | +++ lib/lp/services/scripts/tests/__init__.py 2011-08-03 17:36:12 +0000 |
2409 | @@ -24,7 +24,6 @@ |
2410 | |
2411 | KNOWN_BROKEN = [ |
2412 | # Needs mysqldb module |
2413 | - 'scripts/bugzilla-import.py', |
2414 | 'scripts/migrate-bugzilla-initialcontacts.py', |
2415 | # circular import from hell (IHasOwner). |
2416 | 'scripts/clean-sourceforge-project-entries.py', |
2417 | |
2418 | === removed file 'scripts/bugzilla-import.py' |
2419 | --- scripts/bugzilla-import.py 2010-04-27 19:48:39 +0000 |
2420 | +++ scripts/bugzilla-import.py 1970-01-01 00:00:00 +0000 |
2421 | @@ -1,97 +0,0 @@ |
2422 | -#!/usr/bin/python -S |
2423 | -# |
2424 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
2425 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2426 | - |
2427 | -import sys |
2428 | -import logging |
2429 | -import optparse |
2430 | -import MySQLdb |
2431 | - |
2432 | -# pylint: disable-msg=W0403 |
2433 | -import _pythonpath |
2434 | - |
2435 | -from canonical.config import config |
2436 | -from canonical.lp import initZopeless |
2437 | -from canonical.launchpad.scripts import ( |
2438 | - execute_zcml_for_scripts, logger_options, logger) |
2439 | -from canonical.launchpad.webapp.interaction import setupInteractionByEmail |
2440 | - |
2441 | -from canonical.launchpad.scripts import bugzilla |
2442 | - |
2443 | - |
2444 | -def make_connection(options): |
2445 | - kws = {} |
2446 | - if options.db_name is not None: |
2447 | - kws['db'] = options.db_name |
2448 | - if options.db_user is not None: |
2449 | - kws['user'] = options.db_user |
2450 | - if options.db_password is not None: |
2451 | - kws['passwd'] = options.db_passwd |
2452 | - if options.db_host is not None: |
2453 | - kws['host'] = options.db_host |
2454 | - |
2455 | - return MySQLdb.connect(**kws) |
2456 | - |
2457 | -def main(argv): |
2458 | - parser = optparse.OptionParser( |
2459 | - description=("This script imports bugs from a Bugzilla " |
2460 | - "into Launchpad.")) |
2461 | - |
2462 | - parser.add_option('--component', metavar='COMPONENT', action='append', |
2463 | - help='Limit to this bugzilla component', |
2464 | - type='string', dest='component', default=[]) |
2465 | - parser.add_option('--status', metavar='STATUS,...', action='store', |
2466 | - help='Only import bugs with the given status', |
2467 | - type='string', dest='status', |
2468 | - default=None) |
2469 | - |
2470 | - # MySQL connection details |
2471 | - parser.add_option('-d', '--dbname', metavar='DB', action='store', |
2472 | - help='The MySQL database name', |
2473 | - type='string', dest='db_name', default='bugs_warty') |
2474 | - parser.add_option('-U', '--username', metavar='USER', action='store', |
2475 | - help='The MySQL user name', |
2476 | - type='string', dest='db_user', default=None) |
2477 | - parser.add_option('-p', '--password', metavar='PASSWORD', action='store', |
2478 | - help='The MySQL password', |
2479 | - type='string', dest='db_password', default=None) |
2480 | - parser.add_option('-H', '--host', metavar='HOST', action='store', |
2481 | - help='The MySQL database host', |
2482 | - type='string', dest='db_host', default=None) |
2483 | - |
2484 | - # logging options |
2485 | - logger_options(parser, logging.INFO) |
2486 | - |
2487 | - options, args = parser.parse_args(argv[1:]) |
2488 | - if options.status is not None: |
2489 | - options.status = options.status.split(',') |
2490 | - else: |
2491 | - options.status = [] |
2492 | - |
2493 | - logger(options, 'canonical.launchpad.scripts.bugzilla') |
2494 | - |
2495 | - # don't send email |
2496 | - send_email_data = """ |
2497 | - [zopeless] |
2498 | - send_email: False |
2499 | - """ |
2500 | - config.push('send_email_data', send_email_data) |
2501 | - |
2502 | - execute_zcml_for_scripts() |
2503 | - ztm = initZopeless() |
2504 | - setupInteractionByEmail('bug-importer@launchpad.net') |
2505 | - |
2506 | - db = make_connection(options) |
2507 | - bz = bugzilla.Bugzilla(db) |
2508 | - |
2509 | - bz.importBugs(ztm, |
2510 | - product=['Ubuntu'], |
2511 | - component=options.component, |
2512 | - status=options.status) |
2513 | - |
2514 | - bz.processDuplicates(ztm) |
2515 | - config.pop('send_email_data') |
2516 | - |
2517 | -if __name__ == '__main__': |
2518 | - sys.exit(main(sys.argv)) |
2519 | |
2520 | === removed file 'scripts/migrate-bugzilla-initialcontacts.py' |
2521 | --- scripts/migrate-bugzilla-initialcontacts.py 2011-05-29 01:38:41 +0000 |
2522 | +++ scripts/migrate-bugzilla-initialcontacts.py 1970-01-01 00:00:00 +0000 |
2523 | @@ -1,91 +0,0 @@ |
2524 | -#!/usr/bin/python -S |
2525 | -# |
2526 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
2527 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2528 | - |
2529 | -import logging |
2530 | -import MySQLdb |
2531 | - |
2532 | -import _pythonpath |
2533 | - |
2534 | -from zope.component import getUtility |
2535 | - |
2536 | -from canonical.lp import initZopeless |
2537 | -from canonical.launchpad.scripts import execute_zcml_for_scripts |
2538 | -from canonical.launchpad.interfaces.emailaddress import IEmailAddressSet |
2539 | -from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
2540 | -from lp.app.errors import NotFoundError |
2541 | -from lp.registry.interfaces.person import IPersonSet |
2542 | - |
2543 | - |
2544 | -execute_zcml_for_scripts() |
2545 | -ztm = initZopeless() |
2546 | -logging.basicConfig(level=logging.INFO) |
2547 | - |
2548 | -ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
2549 | -techboard = getUtility(IPersonSet).getByName('techboard') |
2550 | - |
2551 | -def getPerson(email, realname): |
2552 | - # The debzilla user acts as a placeholder for "no specific maintainer". |
2553 | - # We don't create a bug contact record for it. |
2554 | - if email is None or email == 'debzilla@ubuntu.com': |
2555 | - return None |
2556 | - |
2557 | - personset = getUtility(IPersonSet) |
2558 | - person = personset.getByEmail(email) |
2559 | - if person: |
2560 | - return person |
2561 | - |
2562 | - # we mark the bugzilla email as preferred email, since it has been |
2563 | - # validated there. |
2564 | - if email.endswith('@lists.ubuntu.com'): |
2565 | - logging.info('creating team for %s (%s)', email, realname) |
2566 | - person = personset.newTeam(techboard, email[:-17], realname) |
2567 | - email = getUtility(IEmailAddressSet).new(email, person.id) |
2568 | - person.setPreferredEmail(email) |
2569 | - else: |
2570 | - logging.info('creating person for %s (%s)', email, realname) |
2571 | - person, email = personset.createPersonAndEmail(email, |
2572 | - displayname=realname) |
2573 | - person.setPreferredEmail(email) |
2574 | - |
2575 | - return person |
2576 | - |
2577 | - |
2578 | -conn = MySQLdb.connect(db='bugs_warty') |
2579 | -cursor = conn.cursor() |
2580 | - |
2581 | -# big arse query that gets all the default assignees and QA contacts: |
2582 | -cursor.execute( |
2583 | - "SELECT components.name, owner.login_name, owner.realname, " |
2584 | - " qa.login_name, qa.realname " |
2585 | - " FROM components " |
2586 | - " JOIN products ON components.product_id = products.id " |
2587 | - " LEFT JOIN profiles AS owner ON components.initialowner = owner.userid" |
2588 | - " LEFT JOIN profiles AS qa ON components.initialqacontact = qa.userid " |
2589 | - " WHERE products.name = 'Ubuntu'") |
2590 | - |
2591 | -for (component, owneremail, ownername, qaemail, qaname) in cursor.fetchall(): |
2592 | - logging.info('Processing %s', component) |
2593 | - try: |
2594 | - srcpkgname, binpkgname = ubuntu.getPackageNames(component) |
2595 | - except NotFoundError, e: |
2596 | - logging.warning('could not find package name for "%s": %s', component, |
2597 | - str(e)) |
2598 | - continue |
2599 | - |
2600 | - srcpkg = ubuntu.getSourcePackage(srcpkgname) |
2601 | - |
2602 | - # default assignee => maintainer |
2603 | - person = getPerson(owneremail, ownername) |
2604 | - if person: |
2605 | - if not srcpkg.isBugContact(person): |
2606 | - srcpkg.addBugContact(person) |
2607 | - |
2608 | - # QA contact => maintainer |
2609 | - person = getPerson(qaemail, qaname) |
2610 | - if person: |
2611 | - if not srcpkg.isBugContact(person): |
2612 | - srcpkg.addBugContact(person) |
2613 | - |
2614 | -ztm.commit() |
Looks fine. We will want an index, so please add this in:
CREATE INDEX codeimport_ _failure_ bug__idx ON CodeImport( failure_ bug) WHERE failure_bug IS NOT NULL;
patch-2208-06-0.sql