Merge lp:~lifeless/launchpad/merge into lp:launchpad/db-devel

Proposed by Robert Collins
Status: Rejected
Rejected by: Robert Collins
Proposed branch: lp:~lifeless/launchpad/merge
Merge into: lp:launchpad/db-devel
Diff against target: 1823 lines (+855/-273) (has conflicts)
38 files modified
database/schema/security.cfg (+1/-0)
lib/canonical/launchpad/doc/security-teams.txt (+2/-1)
lib/canonical/launchpad/mail/commands.py (+1/-1)
lib/lp/archiveuploader/tests/test_ppauploadprocessor.py (+8/-8)
lib/lp/bugs/browser/bug.py (+18/-9)
lib/lp/bugs/browser/bugtarget.py (+10/-1)
lib/lp/bugs/configure.zcml (+1/-0)
lib/lp/bugs/doc/bug-heat.txt (+22/-0)
lib/lp/bugs/doc/bug.txt (+1/-1)
lib/lp/bugs/doc/bugnotification-email.txt (+3/-3)
lib/lp/bugs/doc/bugnotification-sending.txt (+2/-1)
lib/lp/bugs/interfaces/bug.py (+12/-1)
lib/lp/bugs/model/bug.py (+27/-0)
lib/lp/bugs/scripts/bugheat.py (+1/-0)
lib/lp/bugs/scripts/bugimport.py (+1/-1)
lib/lp/bugs/scripts/tests/test_bugheat.py (+2/-2)
lib/lp/bugs/tests/test_bugchanges.py (+2/-2)
lib/lp/code/browser/branch.py (+13/-14)
lib/lp/code/configure.zcml (+2/-1)
lib/lp/code/errors.py (+23/-0)
lib/lp/code/interfaces/branchmergeproposal.py (+3/-0)
lib/lp/code/interfaces/codeimport.py (+29/-1)
lib/lp/code/interfaces/webservice.py (+11/-0)
lib/lp/code/model/branchmergeproposal.py (+11/-10)
lib/lp/code/model/branchtarget.py (+20/-0)
lib/lp/code/model/codeimport.py (+24/-0)
lib/lp/code/model/tests/test_branchmergeproposals.py (+39/-0)
lib/lp/code/model/tests/test_codeimport.py (+67/-0)
lib/lp/code/stories/webservice/xx-code-import.txt (+368/-168)
lib/lp/services/worlddata/doc/language.txt (+33/-1)
lib/lp/services/worlddata/interfaces/language.py (+3/-2)
lib/lp/services/worlddata/tests/test_doc.py (+12/-1)
lib/lp/services/worlddata/tests/test_language.py (+21/-0)
lib/lp/soyuz/doc/archive.txt (+2/-2)
lib/lp/soyuz/doc/distroseriesqueue-translations.txt (+43/-9)
lib/lp/soyuz/model/archive.py (+1/-1)
lib/lp/soyuz/model/queue.py (+6/-3)
lib/lp/translations/templates/language-index.pt (+10/-29)
Text conflict in lib/lp/code/interfaces/webservice.py
Text conflict in lib/lp/code/model/branchtarget.py
Text conflict in lib/lp/code/stories/webservice/xx-code-import.txt
To merge this branch: bzr merge lp:~lifeless/launchpad/merge
Reviewer Review Type Date Requested Status
Canonical Launchpad Engineering Pending
Review via email: mp+23521@code.launchpad.net

Description of the change

Fix a few bugs around merge proposals:
 - when transitioning out of 'queued', always dequeue.
 - permit transitioning to merge-failed
 - use the reviewed revid by default when queueing

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2=== modified file 'configs/testrunner/launchpad-lazr.conf'
3=== modified file 'database/schema/security.cfg'
4--- database/schema/security.cfg 2010-04-13 20:23:15 +0000
5+++ database/schema/security.cfg 2010-04-16 04:01:13 +0000
6@@ -1263,6 +1263,7 @@
7 public.question = SELECT
8 public.questionbug = SELECT
9 public.distribution = SELECT
10+public.distributionsourcepackage = SELECT, INSERT, UPDATE
11 public.distroseries = SELECT
12 public.sourcepackagename = SELECT
13 public.sourcepackagerelease = SELECT
14
15=== modified file 'lib/canonical/launchpad/doc/security-teams.txt'
16--- lib/canonical/launchpad/doc/security-teams.txt 2009-08-14 12:59:56 +0000
17+++ lib/canonical/launchpad/doc/security-teams.txt 2010-04-16 04:01:13 +0000
18@@ -268,7 +268,8 @@
19 >>> bug.addTask(owner=reporter, target=distribution)
20 <BugTask at ...>
21 >>> old_state = Snapshot(bug, providing=IBug)
22- >>> bug.security_related = True
23+ >>> bug.setSecurityRelated(True)
24+ True
25 >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related']))
26 >>> for subscriber_name in sorted(
27 ... s.displayname for s in bug.getDirectSubscribers()):
28
29=== modified file 'lib/canonical/launchpad/mail/commands.py'
30--- lib/canonical/launchpad/mail/commands.py 2010-02-17 11:13:06 +0000
31+++ lib/canonical/launchpad/mail/commands.py 2010-04-16 04:01:13 +0000
32@@ -279,7 +279,7 @@
33 edited = True
34 edited_fields.add('private')
35 if context.security_related != security_related:
36- context.security_related = security_related
37+ context.setSecurityRelated(security_related)
38 edited = True
39 edited_fields.add('security_related')
40
41
42=== modified file 'lib/lp/archiveuploader/tests/test_ppauploadprocessor.py'
43--- lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-04-12 15:02:30 +0000
44+++ lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-04-16 04:01:13 +0000
45@@ -1252,9 +1252,9 @@
46 the size of the upload plus the current PPA size must be smaller
47 than the PPA.authorized_size, otherwise the upload will be rejected.
48 """
49- # Stuff 1024 MiB in name16 PPA, so anything will be above the
50- # default quota limit, 1024 MiB.
51- self._fillArchive(self.name16.archive, 1024 * (2 ** 20))
52+ # Stuff 2048 MiB in name16 PPA, so anything will be above the
53+ # default quota limit, 2048 MiB.
54+ self._fillArchive(self.name16.archive, 2048 * (2 ** 20))
55
56 upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu")
57 upload_results = self.processUpload(self.uploadprocessor, upload_dir)
58@@ -1267,7 +1267,7 @@
59 contents = [
60 "Subject: bar_1.0-1_source.changes rejected",
61 "Rejected:",
62- "PPA exceeded its size limit (1024.00 of 1024.00 MiB). "
63+ "PPA exceeded its size limit (2048.00 of 2048.00 MiB). "
64 "Ask a question in https://answers.launchpad.net/soyuz/ "
65 "if you need more space."]
66 self.assertEmail(contents)
67@@ -1278,9 +1278,9 @@
68 The system start warning users for uploads exceeding 95 % of
69 the current size limit.
70 """
71- # Stuff 973 MiB into name16 PPA, approximately 95 % of
72- # the default quota limit, 1024 MiB.
73- self._fillArchive(self.name16.archive, 973 * (2 ** 20))
74+ # Stuff 1945 MiB into name16 PPA, approximately 95 % of
75+ # the default quota limit, 2048 MiB.
76+ self._fillArchive(self.name16.archive, 2000 * (2 ** 20))
77
78 # Ensure the warning is sent in the acceptance notification.
79 upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu")
80@@ -1288,7 +1288,7 @@
81 contents = [
82 "Subject: [PPA name16] [ubuntu/breezy] bar 1.0-1 (Accepted)",
83 "Upload Warnings:",
84- "PPA exceeded 95 % of its size limit (973.00 of 1024.00 MiB). "
85+ "PPA exceeded 95 % of its size limit (2000.00 of 2048.00 MiB). "
86 "Ask a question in https://answers.launchpad.net/soyuz/ "
87 "if you need more space."]
88 self.assertEmail(contents)
89
90=== modified file 'lib/lp/bugs/browser/bug.py'
91--- lib/lp/bugs/browser/bug.py 2010-04-07 11:28:32 +0000
92+++ lib/lp/bugs/browser/bug.py 2010-04-16 04:01:13 +0000
93@@ -673,7 +673,7 @@
94 page_title = label
95
96 def setUpFields(self):
97- """Make the read-only version of `private` writable."""
98+ """Make the read-only version of the form fields writable."""
99 private_field = Bool(
100 __name__='private',
101 title=_("This bug report should be private"),
102@@ -681,10 +681,17 @@
103 description=_("Private bug reports are visible only to "
104 "their subscribers."),
105 default=False)
106+ security_related_field = Bool(
107+ __name__='security_related',
108+ title=_("This bug is a security vulnerability"),
109+ required=False, default=False)
110+
111 super(BugSecrecyEditView, self).setUpFields()
112 self.form_fields = self.form_fields.omit('private')
113+ self.form_fields = self.form_fields.omit('security_related')
114 self.form_fields = (
115- formlib.form.Fields(private_field) + self.form_fields)
116+ formlib.form.Fields(private_field) +
117+ formlib.form.Fields(security_related_field))
118
119 @property
120 def initial_values(self):
121@@ -705,16 +712,18 @@
122 bug_before_modification = Snapshot(
123 bug, providing=providedBy(bug))
124 private = data.pop('private')
125+ security_related = data.pop('security_related')
126 private_changed = bug.setPrivate(
127 private, getUtility(ILaunchBag).user)
128- if private_changed:
129- # Although the call to updateBugFromData later on will
130- # send notification of changes, it will only do so if it
131- # makes the change. We have applied the 'private' change
132- # already, so updateBugFromData will only send an event if
133- # 'security_related' is changed, and we can't have that.
134+ security_related_changed = bug.setSecurityRelated(security_related)
135+ if private_changed or security_related_changed:
136+ changed_fields = []
137+ if private_changed:
138+ changed_fields.append('private')
139+ if security_related_changed:
140+ changed_fields.append('security_related')
141 notify(ObjectModifiedEvent(
142- bug, bug_before_modification, ['private']))
143+ bug, bug_before_modification, changed_fields))
144
145 # Apply other changes.
146 self.updateBugFromData(data)
147
148=== modified file 'lib/lp/bugs/browser/bugtarget.py'
149--- lib/lp/bugs/browser/bugtarget.py 2010-03-16 16:23:50 +0000
150+++ lib/lp/bugs/browser/bugtarget.py 2010-04-16 04:01:13 +0000
151@@ -36,7 +36,7 @@
152 from zope.interface import implements
153 from zope.publisher.interfaces import NotFound
154 from zope.publisher.interfaces.browser import IBrowserPublisher
155-from zope.schema import Choice
156+from zope.schema import Bool, Choice
157 from zope.schema.vocabulary import SimpleVocabulary
158
159 from canonical.cachedproperty import cachedproperty
160@@ -45,6 +45,7 @@
161 from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource
162 from lp.bugs.interfaces.bug import IBug
163 from lp.bugs.interfaces.bugtask import BugTaskSearchParams
164+from canonical.launchpad import _
165 from canonical.launchpad.browser.feeds import (
166 BugFeedLink, BugTargetLatestBugsFeedLink, FeedsMixin)
167 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
168@@ -297,6 +298,14 @@
169 self.form_fields = self.form_fields.omit('subscribe_to_existing_bug')
170 self.form_fields += formlib.form.Fields(subscribe_field)
171
172+ security_related_field = Bool(
173+ __name__='security_related',
174+ title=_("This bug is a security vulnerability"),
175+ required=False, default=False)
176+
177+ self.form_fields = self.form_fields.omit('security_related')
178+ self.form_fields += formlib.form.Fields(security_related_field)
179+
180 def contextUsesMalone(self):
181 """Does the context use Malone as its official bugtracker?"""
182 if IProjectGroup.providedBy(self.context):
183
184=== modified file 'lib/lp/bugs/configure.zcml'
185--- lib/lp/bugs/configure.zcml 2010-04-08 08:55:10 +0000
186+++ lib/lp/bugs/configure.zcml 2010-04-16 04:01:13 +0000
187@@ -681,6 +681,7 @@
188 expireNotifications
189 setStatus
190 setPrivate
191+ setSecurityRelated
192 convertToQuestion
193 markUserAffected
194 addTask
195
196=== modified file 'lib/lp/bugs/doc/bug-heat.txt'
197--- lib/lp/bugs/doc/bug-heat.txt 2010-04-12 07:11:47 +0000
198+++ lib/lp/bugs/doc/bug-heat.txt 2010-04-16 04:01:13 +0000
199@@ -29,6 +29,28 @@
200 datetime.datetime(..., tzinfo=<UTC>)
201
202
203+Adjusting bug heat in transaction
204+---------------------------------
205+
206+Sometimes, when a bug changes, we want to see the changes reflected in the bug's
207+heat value immidiately, without waiting for heat to be recalculated. Currently
208+we adjust heat immidiately for bug privacy and security.
209+
210+ >>> bug_owner = factory.makePerson()
211+ >>> bug = factory.makeBug(owner=bug_owner)
212+ >>> bug.heat
213+ 0
214+ >>> changed = bug.setPrivate(True, bug_owner)
215+ >>> bug.heat
216+ 150
217+ >>> changed = bug.setSecurityRelated(True)
218+ >>> bug.heat
219+ 400
220+ >>> changed = bug.setPrivate(False, bug_owner)
221+ >>> bug.heat
222+ 250
223+
224+
225 Getting bugs whose heat is outdated
226 -----------------------------------
227
228
229=== modified file 'lib/lp/bugs/doc/bug.txt'
230--- lib/lp/bugs/doc/bug.txt 2010-02-11 05:08:47 +0000
231+++ lib/lp/bugs/doc/bug.txt 2010-04-16 04:01:13 +0000
232@@ -747,7 +747,7 @@
233
234 >>> firefox_bug.security_related
235 False
236- >>> firefox_bug.security_related = True
237+ >>> changed = firefox_bug.setSecurityRelated(True)
238
239 >>> bug_security_changed = ObjectModifiedEvent(
240 ... firefox_bug, bug_before_modification, ["security_related"])
241
242=== modified file 'lib/lp/bugs/doc/bugnotification-email.txt'
243--- lib/lp/bugs/doc/bugnotification-email.txt 2010-01-20 17:09:40 +0000
244+++ lib/lp/bugs/doc/bugnotification-email.txt 2010-04-16 04:01:13 +0000
245@@ -85,7 +85,7 @@
246
247 New security related bugs are sent with a prominent warning:
248
249- >>> bug_four.security_related = True
250+ >>> changed = bug_four.setSecurityRelated(True)
251
252 >>> subject, body = generate_bug_add_email(bug_four)
253 >>> subject
254@@ -202,7 +202,7 @@
255
256 >>> edited_bug.setPrivate(True, getUtility(ILaunchBag).user)
257 True
258- >>> edited_bug.security_related = True
259+ >>> changed = edited_bug.setSecurityRelated(True)
260 >>> bug_delta = BugDelta(
261 ... bug=edited_bug,
262 ... bugurl="http://www.example.com/bugs/2",
263@@ -225,7 +225,7 @@
264
265 >>> edited_bug.setPrivate(False, getUtility(ILaunchBag).user)
266 True
267- >>> edited_bug.security_related = False
268+ >>> changed = edited_bug.setSecurityRelated(False)
269 >>> bug_delta = BugDelta(
270 ... bug=edited_bug,
271 ... bugurl="http://www.example.com/bugs/2",
272
273=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
274--- lib/lp/bugs/doc/bugnotification-sending.txt 2010-04-01 03:14:47 +0000
275+++ lib/lp/bugs/doc/bugnotification-sending.txt 2010-04-16 04:01:13 +0000
276@@ -1199,7 +1199,8 @@
277 The presence of the security flag on a bug is, surprise, denoted by a
278 simple "yes":
279
280- >>> bug_three.security_related = True
281+ >>> bug_three.setSecurityRelated(True)
282+ True
283 >>> bug_three.security_related
284 True
285
286
287=== modified file 'lib/lp/bugs/interfaces/bug.py'
288--- lib/lp/bugs/interfaces/bug.py 2010-04-12 14:48:34 +0000
289+++ lib/lp/bugs/interfaces/bug.py 2010-04-16 04:01:13 +0000
290@@ -207,7 +207,7 @@
291 readonly=True))
292 security_related = exported(
293 Bool(title=_("This bug is a security vulnerability"),
294- required=False, default=False))
295+ required=False, default=False, readonly=True))
296 displayname = TextLine(title=_("Text of the form 'Bug #X"),
297 readonly=True)
298 activity = Attribute('SQLObject.Multijoin of IBugActivity')
299@@ -705,6 +705,17 @@
300 Return True if a change is made, False otherwise.
301 """
302
303+ @mutator_for(security_related)
304+ @operation_parameters(security_related=copy_field(security_related))
305+ @export_write_operation()
306+ def setSecurityRelated(security_related):
307+ """Set bug security.
308+
309+ :security_related: True/False.
310+
311+ Return True if a change is made, False otherwise.
312+ """
313+
314 def getBugTask(target):
315 """Return the bugtask with the specified target.
316
317
318=== modified file 'lib/lp/bugs/model/bug.py'
319--- lib/lp/bugs/model/bug.py 2010-04-12 14:48:34 +0000
320+++ lib/lp/bugs/model/bug.py 2010-04-16 04:01:13 +0000
321@@ -83,6 +83,7 @@
322 from lp.bugs.interfaces.bugtracker import BugTrackerType
323 from lp.bugs.interfaces.bugwatch import IBugWatchSet
324 from lp.bugs.interfaces.cve import ICveSet
325+from lp.bugs.scripts.bugheat import BugHeatConstants
326 from lp.bugs.model.bugattachment import BugAttachment
327 from lp.bugs.model.bugbranch import BugBranch
328 from lp.bugs.model.bugcve import BugCve
329@@ -1351,10 +1352,33 @@
330 self.who_made_private = None
331 self.date_made_private = None
332
333+ # Correct the heat for the bug immediately, so that we don't have
334+ # to wait for the next calculation job for the adjusted heat.
335+ if private:
336+ self.setHeat(self.heat + BugHeatConstants.PRIVACY)
337+ else:
338+ self.setHeat(self.heat - BugHeatConstants.PRIVACY)
339+
340 return True # Changed.
341 else:
342 return False # Not changed.
343
344+ def setSecurityRelated(self, security_related):
345+ """Setter for the `security_related` property."""
346+ if self.security_related != security_related:
347+ self.security_related = security_related
348+
349+ # Correct the heat for the bug immediately, so that we don't have
350+ # to wait for the next calculation job for the adjusted heat.
351+ if security_related:
352+ self.setHeat(self.heat + BugHeatConstants.SECURITY)
353+ else:
354+ self.setHeat(self.heat - BugHeatConstants.SECURITY)
355+
356+ return True # Changed
357+ else:
358+ return False # Unchanged
359+
360 def getBugTask(self, target):
361 """See `IBug`."""
362 for bugtask in self.bugtasks:
363@@ -1540,6 +1564,9 @@
364 if timestamp is None:
365 timestamp = UTC_NOW
366
367+ if heat < 0:
368+ heat = 0
369+
370 self.heat = heat
371 self.heat_last_updated = timestamp
372 for task in self.bugtasks:
373
374=== modified file 'lib/lp/bugs/scripts/bugheat.py'
375--- lib/lp/bugs/scripts/bugheat.py 2010-03-04 19:49:08 +0000
376+++ lib/lp/bugs/scripts/bugheat.py 2010-04-16 04:01:13 +0000
377@@ -6,6 +6,7 @@
378 __metaclass__ = type
379 __all__ = [
380 'BugHeatCalculator',
381+ 'BugHeatConstants',
382 ]
383
384 from datetime import datetime
385
386=== modified file 'lib/lp/bugs/scripts/bugimport.py'
387--- lib/lp/bugs/scripts/bugimport.py 2009-09-11 14:59:08 +0000
388+++ lib/lp/bugs/scripts/bugimport.py 2010-04-16 04:01:13 +0000
389@@ -328,7 +328,7 @@
390
391 # set up bug
392 bug.setPrivate(get_value(bugnode, 'private') == 'True', owner)
393- bug.security_related = (
394+ bug.setSecurityRelated(
395 get_value(bugnode, 'security_related') == 'True')
396 bug.name = get_value(bugnode, 'nickname')
397 description = get_value(bugnode, 'description')
398
399=== modified file 'lib/lp/bugs/scripts/tests/test_bugheat.py'
400--- lib/lp/bugs/scripts/tests/test_bugheat.py 2010-03-03 16:05:57 +0000
401+++ lib/lp/bugs/scripts/tests/test_bugheat.py 2010-04-16 04:01:13 +0000
402@@ -155,7 +155,7 @@
403
404 # If, on the other hand, the bug is security_related,
405 # _getHeatFromSecurity() will return BugHeatConstants.SECURITY
406- self.bug.security_related = True
407+ self.bug.setSecurityRelated(True)
408 self.assertEqual(
409 BugHeatConstants.SECURITY, self.calculator._getHeatFromSecurity())
410
411@@ -179,7 +179,7 @@
412 dupe = self.factory.makeBug()
413 dupe.duplicateof = self.bug
414 self.bug.setPrivate(True, self.bug.owner)
415- self.bug.security_related = True
416+ self.bug.setSecurityRelated(True)
417
418 expected_heat += (
419 BugHeatConstants.DUPLICATE +
420
421=== modified file 'lib/lp/bugs/tests/test_bugchanges.py'
422--- lib/lp/bugs/tests/test_bugchanges.py 2009-12-05 18:37:28 +0000
423+++ lib/lp/bugs/tests/test_bugchanges.py 2010-04-16 04:01:13 +0000
424@@ -562,7 +562,7 @@
425 def test_mark_as_security_vulnerability(self):
426 # Marking a bug as a security vulnerability adds to the bug's
427 # activity log and sends a notification.
428- self.bug.security_related = False
429+ self.bug.setSecurityRelated(False)
430 self.changeAttribute(self.bug, 'security_related', True)
431
432 security_change_activity = {
433@@ -586,7 +586,7 @@
434 def test_unmark_as_security_vulnerability(self):
435 # Unmarking a bug as a security vulnerability adds to the
436 # bug's activity log and sends a notification.
437- self.bug.security_related = True
438+ self.bug.setSecurityRelated(True)
439 self.changeAttribute(self.bug, 'security_related', False)
440
441 security_change_activity = {
442
443=== modified file 'lib/lp/code/browser/branch.py'
444--- lib/lp/code/browser/branch.py 2010-03-25 15:28:49 +0000
445+++ lib/lp/code/browser/branch.py 2010-04-16 04:01:13 +0000
446@@ -81,10 +81,12 @@
447 from lp.code.browser.branchmergeproposal import (
448 latest_proposals_for_each_branch)
449 from lp.code.enums import (
450- BranchLifecycleStatus, BranchType, CodeImportJobState,
451+ BranchLifecycleStatus, BranchType,
452 CodeImportResultStatus, CodeImportReviewStatus, RevisionControlSystems,
453 UICreatableBranchType)
454-from lp.code.errors import InvalidBranchMergeProposal
455+from lp.code.errors import (
456+ CodeImportAlreadyRequested, CodeImportAlreadyRunning,
457+ CodeImportNotInReviewedState, InvalidBranchMergeProposal)
458 from lp.code.interfaces.branch import (
459 BranchCreationForbidden, BranchExists, IBranch,
460 user_has_special_branch_access)
461@@ -1317,26 +1319,23 @@
462
463 @action('Import Now', name='request')
464 def request_import_action(self, action, data):
465- if self.context.code_import.import_job is None:
466+ try:
467+ self.context.code_import.requestImport(
468+ self.user, error_if_already_requested=True)
469+ self.request.response.addNotification(
470+ "Import will run as soon as possible.")
471+ except CodeImportNotInReviewedState:
472 self.request.response.addNotification(
473 "This import is no longer being updated automatically.")
474- elif (self.context.code_import.import_job.state !=
475- CodeImportJobState.PENDING):
476- assert (self.context.code_import.import_job.state ==
477- CodeImportJobState.RUNNING)
478+ except CodeImportAlreadyRunning:
479 self.request.response.addNotification(
480 "The import is already running.")
481- elif self.context.code_import.import_job.requesting_user is not None:
482- user = self.context.code_import.import_job.requesting_user
483+ except CodeImportAlreadyRequested, e:
484+ user = e.requesting_user
485 adapter = queryAdapter(user, IPathAdapter, 'fmt')
486 self.request.response.addNotification(
487 structured("The import has already been requested by %s." %
488 adapter.link(None)))
489- else:
490- getUtility(ICodeImportJobWorkflow).requestJob(
491- self.context.code_import.import_job, self.user)
492- self.request.response.addNotification(
493- "Import will run as soon as possible.")
494
495 @property
496 def prefix(self):
497
498=== modified file 'lib/lp/code/configure.zcml'
499--- lib/lp/code/configure.zcml 2010-04-13 23:46:06 +0000
500+++ lib/lp/code/configure.zcml 2010-04-16 04:01:13 +0000
501@@ -840,7 +840,8 @@
502 getImportDetailsForDisplay"/>
503 <require
504 permission="launchpad.AnyPerson"
505- attributes="tryFailingImportAgain"/>
506+ attributes="tryFailingImportAgain
507+ requestImport"/>
508 <require
509 permission="launchpad.Edit"
510 attributes="updateFromData"/>
511
512=== modified file 'lib/lp/code/errors.py'
513--- lib/lp/code/errors.py 2010-04-05 21:38:40 +0000
514+++ lib/lp/code/errors.py 2010-04-16 04:01:13 +0000
515@@ -8,6 +8,9 @@
516 'BadBranchMergeProposalSearchContext',
517 'BadStateTransition',
518 'BranchMergeProposalExists',
519+ 'CodeImportAlreadyRequested',
520+ 'CodeImportAlreadyRunning',
521+ 'CodeImportNotInReviewedState',
522 'ClaimReviewFailed',
523 'InvalidBranchMergeProposal',
524 'ReviewNotPending',
525@@ -68,3 +71,23 @@
526
527 class UnknownBranchTypeError(Exception):
528 """Raised when the user specifies an unrecognized branch type."""
529+
530+
531+class CodeImportNotInReviewedState(Exception):
532+ """Raised when the user requests an import of a non-automatic import."""
533+
534+ webservice_error(400)
535+
536+
537+class CodeImportAlreadyRequested(Exception):
538+ """Raised when the user requests an import that is already requested."""
539+
540+ def __init__(self, msg, requesting_user):
541+ super(CodeImportAlreadyRequested, self).__init__(msg)
542+ self.requesting_user = requesting_user
543+
544+
545+class CodeImportAlreadyRunning(Exception):
546+ """Raised when the user requests an import that is already running."""
547+
548+ webservice_error(400)
549
550=== modified file 'lib/lp/code/interfaces/branchmergeproposal.py'
551--- lib/lp/code/interfaces/branchmergeproposal.py 2010-04-01 05:08:47 +0000
552+++ lib/lp/code/interfaces/branchmergeproposal.py 2010-04-16 04:01:13 +0000
553@@ -347,6 +347,9 @@
554 If the proposal is not in the Approved state before this method
555 is called, approveBranch is called with the reviewer and revision_id
556 specified.
557+
558+ If None is supplied as the revision_id, the proposals
559+ reviewed_revision_id is used.
560 """
561
562 def dequeue():
563
564=== modified file 'lib/lp/code/interfaces/codeimport.py'
565--- lib/lp/code/interfaces/codeimport.py 2010-04-01 20:58:42 +0000
566+++ lib/lp/code/interfaces/codeimport.py 2010-04-16 04:01:13 +0000
567@@ -25,7 +25,8 @@
568 from lp.code.interfaces.branch import IBranch
569
570 from lazr.restful.declarations import (
571- export_as_webservice_entry, exported)
572+ call_with, export_as_webservice_entry, exported, export_write_operation,
573+ REQUEST_USER)
574 from lazr.restful.fields import ReferenceChoice
575
576
577@@ -177,6 +178,33 @@
578 :param user: the user who is requesting the import be tried again.
579 """
580
581+ @call_with(requester=REQUEST_USER)
582+ @export_write_operation()
583+ def requestImport(requester, error_if_already_requested=False):
584+ """Request that an import be tried soon.
585+
586+ This method will schedule an import to happen soon for this branch.
587+
588+ The import must be in the Reviewed state, if not then a
589+ CodeImportNotInReviewedState error will be thrown. If using the
590+ API then a status code of 400 will result.
591+
592+ If the import is already running then a CodeImportAlreadyRunning
593+ error will be thrown. If using the API then a status code of
594+ 400 will result.
595+
596+ The two cases can be distinguished over the API by seeing if the
597+ exception names appear in the body of the response.
598+
599+ If used over the API and the request has already been made then this
600+ method will silently do nothing.
601+ If called internally then the error_if_already_requested parameter
602+ controls whether a CodeImportAlreadyRequested exception will be
603+ thrown in that situation.
604+
605+ :return: None
606+ """
607+
608
609 class ICodeImportSet(Interface):
610 """Interface representing the set of code imports."""
611
612=== modified file 'lib/lp/code/interfaces/webservice.py'
613--- lib/lp/code/interfaces/webservice.py 2010-04-01 23:04:10 +0000
614+++ lib/lp/code/interfaces/webservice.py 2010-04-16 04:01:13 +0000
615@@ -3,12 +3,23 @@
616
617 """All the interfaces that are exposed through the webservice."""
618
619+<<<<<<< TREE
620 # The exceptions are imported so that they can produce the special
621 # status code defined by webservice_error when they are raised.
622 from lp.code.errors import BranchMergeProposalExists
623 from lp.code.interfaces.branch import (
624 IBranch, IBranchSet, BranchCreatorNotMemberOfOwnerTeam,
625 BranchCreatorNotOwner, BranchExists)
626+=======
627+# The exceptions are imported so that they can produce the special
628+# status code defined by webservice_error when they are raised.
629+from lp.code.errors import (
630+ BranchMergeProposalExists, CodeImportAlreadyRunning,
631+ CodeImportNotInReviewedState)
632+from lp.code.interfaces.branch import (
633+ IBranch, IBranchSet, BranchCreatorNotMemberOfOwnerTeam,
634+ BranchCreatorNotOwner, BranchExists)
635+>>>>>>> MERGE-SOURCE
636 from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
637 from lp.code.interfaces.branchsubscription import IBranchSubscription
638 from lp.code.interfaces.codeimport import ICodeImport
639
640=== modified file 'lib/lp/code/model/branchmergeproposal.py'
641--- lib/lp/code/model/branchmergeproposal.py 2010-04-01 04:48:01 +0000
642+++ lib/lp/code/model/branchmergeproposal.py 2010-04-16 04:01:13 +0000
643@@ -84,9 +84,10 @@
644 if (next_state == rejected and not valid_reviewer):
645 return False
646 # Non-reviewers can toggle between code_approved and queued, but not
647- # make anything else approved or queued.
648+ # make anything else approved or queued. They can also take merge failed
649+ # and requeue or bounce all the way out to approved again.
650 elif (next_state in (code_approved, queued) and
651- from_state not in (code_approved, queued)
652+ from_state not in (code_approved, queued, merge_failed)
653 and not valid_reviewer):
654 return False
655 else:
656@@ -325,23 +326,23 @@
657 # XXX - rockstar - 9 Oct 2008 - jml suggested in a review that this
658 # would be better as a dict mapping.
659 # See bug #281060.
660+ if (self.queue_status == BranchMergeProposalStatus.QUEUED and
661+ status != BranchMergeProposalStatus.QUEUED):
662+ self.dequeue()
663 if status == BranchMergeProposalStatus.WORK_IN_PROGRESS:
664 self.setAsWorkInProgress()
665 elif status == BranchMergeProposalStatus.NEEDS_REVIEW:
666 self.requestReview()
667 elif status == BranchMergeProposalStatus.CODE_APPROVED:
668- # Other half of the edge case. If the status is currently queued,
669- # we need to dequeue, otherwise we just approve the branch.
670- if self.queue_status == BranchMergeProposalStatus.QUEUED:
671- self.dequeue()
672- else:
673- self.approveBranch(user, revision_id)
674+ self.approveBranch(user, revision_id)
675 elif status == BranchMergeProposalStatus.REJECTED:
676 self.rejectBranch(user, revision_id)
677 elif status == BranchMergeProposalStatus.QUEUED:
678 self.enqueue(user, revision_id)
679 elif status == BranchMergeProposalStatus.MERGED:
680 self.markAsMerged(merge_reporter=user)
681+ elif status == BranchMergeProposalStatus.MERGE_FAILED:
682+ self._transitionToState(status)
683 else:
684 raise AssertionError('Unexpected queue status: ' % status)
685
686@@ -379,7 +380,7 @@
687
688 def _reviewProposal(self, reviewer, next_state, revision_id,
689 _date_reviewed=None):
690- """Set the proposal to one of the two review statuses."""
691+ """Set the proposal to next_state."""
692 # Check the reviewer can review the code for the target branch.
693 old_state = self.queue_status
694 if not self.target_branch.isPersonTrustedReviewer(reviewer):
695@@ -433,7 +434,7 @@
696 self.queue_status = BranchMergeProposalStatus.QUEUED
697 self.queue_position = position
698 self.queuer = queuer
699- self.queued_revision_id = revision_id
700+ self.queued_revision_id = revision_id or self.reviewed_revision_id
701 self.date_queued = UTC_NOW
702 self.syncUpdate()
703
704
705=== modified file 'lib/lp/code/model/branchtarget.py'
706--- lib/lp/code/model/branchtarget.py 2010-04-14 17:44:00 +0000
707+++ lib/lp/code/model/branchtarget.py 2010-04-16 04:01:13 +0000
708@@ -337,6 +337,26 @@
709 branch.sourcepackagename = None
710
711
712+<<<<<<< TREE
713+=======
714+class ProductSeriesBranchTarget(ProductBranchTarget):
715+
716+ def __init__(self, productseries):
717+ ProductBranchTarget.__init__(self, productseries.product)
718+ self.productseries = productseries
719+
720+ @property
721+ def context(self):
722+ """See `IBranchTarget`."""
723+ return self.productseries
724+
725+ @property
726+ def supports_code_imports(self):
727+ """See `IBranchTarget`."""
728+ return False
729+
730+
731+>>>>>>> MERGE-SOURCE
732 def get_canonical_url_data_for_target(branch_target):
733 """Return the `ICanonicalUrlData` for an `IBranchTarget`."""
734 return ICanonicalUrlData(branch_target.context)
735
736=== modified file 'lib/lp/code/model/codeimport.py'
737--- lib/lp/code/model/codeimport.py 2010-04-01 20:58:42 +0000
738+++ lib/lp/code/model/codeimport.py 2010-04-16 04:01:13 +0000
739@@ -37,6 +37,9 @@
740 from lp.code.enums import (
741 BranchType, CodeImportJobState, CodeImportResultStatus,
742 CodeImportReviewStatus, RevisionControlSystems)
743+from lp.code.errors import (
744+ CodeImportAlreadyRequested, CodeImportAlreadyRunning,
745+ CodeImportNotInReviewedState)
746 from lp.code.interfaces.codeimport import ICodeImport, ICodeImportSet
747 from lp.code.interfaces.codeimportevent import ICodeImportEventSet
748 from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow
749@@ -196,6 +199,27 @@
750 {'review_status': CodeImportReviewStatus.REVIEWED}, user)
751 getUtility(ICodeImportJobWorkflow).requestJob(self.import_job, user)
752
753+ def requestImport(self, requester, error_if_already_requested=False):
754+ """See `ICodeImport`."""
755+ if self.import_job is None: # not in automatic mode
756+ raise CodeImportNotInReviewedState("This code import is %s, and "
757+ "must be Reviewed for you to call requestImport."
758+ % self.review_status.name)
759+ if (self.import_job.state != CodeImportJobState.PENDING):
760+ assert (self.import_job.state == CodeImportJobState.RUNNING)
761+ # Already running
762+ raise CodeImportAlreadyRunning("This code import is already "
763+ "running.")
764+ elif self.import_job.requesting_user is not None:
765+ if error_if_already_requested:
766+ raise CodeImportAlreadyRequested("This code import has "
767+ "already been requested to run.",
768+ self.import_job.requesting_user)
769+ else:
770+ getUtility(ICodeImportJobWorkflow).requestJob(
771+ self.import_job, requester)
772+ return None
773+
774
775 class CodeImportSet:
776 """See `ICodeImportSet`."""
777
778=== modified file 'lib/lp/code/model/tests/test_branchmergeproposals.py'
779--- lib/lp/code/model/tests/test_branchmergeproposals.py 2010-04-06 03:37:16 +0000
780+++ lib/lp/code/model/tests/test_branchmergeproposals.py 2010-04-16 04:01:13 +0000
781@@ -261,6 +261,20 @@
782 """We can go from merge failed to any other state."""
783 self.assertAllTransitionsGood(BranchMergeProposalStatus.MERGE_FAILED)
784
785+ def test_transition_from_merge_failed_to_queued_non_reviewer(self):
786+ # Contributors can requeue to retry after environmental issues fail a
787+ # merge.
788+ proposal = self.factory.makeBranchMergeProposal()
789+ self.assertFalse(proposal.target_branch.isPersonTrustedReviewer(
790+ proposal.source_branch.owner))
791+ # It is always valid to go to the same state.
792+ self.assertValidTransitions(set([
793+ BranchMergeProposalStatus.MERGE_FAILED,
794+ BranchMergeProposalStatus.CODE_APPROVED,
795+ BranchMergeProposalStatus.QUEUED]),
796+ proposal, BranchMergeProposalStatus.QUEUED,
797+ proposal.source_branch.owner)
798+
799 def test_transitions_from_queued_dequeue(self):
800 # When a proposal is dequeued it is set to code approved, and the
801 # queue position is reset.
802@@ -322,6 +336,19 @@
803 self.target_branch = self.factory.makeProductBranch()
804 login_person(self.target_branch.owner)
805
806+ def test_set_status_approved_to_queued(self):
807+ # setState can change an approved merge proposal to Work In Progress,
808+ # which will set the revision id to the reviewed revision id if not
809+ # supplied.
810+ proposal = self.factory.makeBranchMergeProposal(
811+ target_branch=self.target_branch,
812+ set_state=BranchMergeProposalStatus.CODE_APPROVED)
813+ proposal.approveBranch(proposal.target_branch.owner, '250')
814+ proposal.setStatus(BranchMergeProposalStatus.QUEUED)
815+ self.assertEqual(proposal.queue_status,
816+ BranchMergeProposalStatus.QUEUED)
817+ self.assertEqual(proposal.queued_revision_id, '250')
818+
819 def test_set_status_approved_to_work_in_progress(self):
820 # setState can change an approved merge proposal to Work In Progress.
821 proposal = self.factory.makeBranchMergeProposal(
822@@ -331,6 +358,18 @@
823 self.assertEqual(proposal.queue_status,
824 BranchMergeProposalStatus.WORK_IN_PROGRESS)
825
826+ def test_set_status_queued_to_merge_failed(self):
827+ proposal = self.factory.makeBranchMergeProposal(
828+ target_branch=self.target_branch,
829+ set_state=BranchMergeProposalStatus.QUEUED)
830+ proposal.setStatus(BranchMergeProposalStatus.MERGE_FAILED)
831+ self.assertEqual(proposal.queue_status,
832+ BranchMergeProposalStatus.MERGE_FAILED)
833+ self.assertEqual(proposal.queuer, None)
834+ self.assertEqual(proposal.queued_revision_id, None)
835+ self.assertEqual(proposal.date_queued, None)
836+ self.assertEqual(proposal.queue_position, None)
837+
838 def test_set_status_wip_to_needs_review(self):
839 # setState can change the merge proposal to Needs Review.
840 proposal = self.factory.makeBranchMergeProposal(
841
842=== modified file 'lib/lp/code/model/tests/test_codeimport.py'
843--- lib/lp/code/model/tests/test_codeimport.py 2010-04-01 20:58:42 +0000
844+++ lib/lp/code/model/tests/test_codeimport.py 2010-04-16 04:01:13 +0000
845@@ -12,6 +12,11 @@
846 from zope.component import getUtility
847 from zope.security.proxy import removeSecurityProxy
848
849+from canonical.launchpad.testing.codeimporthelpers import (
850+ make_running_import)
851+from lp.code.errors import (
852+ CodeImportAlreadyRequested, CodeImportAlreadyRunning,
853+ CodeImportNotInReviewedState)
854 from lp.code.model.codeimport import CodeImportSet
855 from lp.code.model.codeimportevent import CodeImportEvent
856 from lp.code.model.codeimportjob import CodeImportJob, CodeImportJobSet
857@@ -620,5 +625,67 @@
858 requester, code_import.import_job.requesting_user)
859
860
861+class TestRequestImport(TestCaseWithFactory):
862+ """Tests for `ICodeImport.requestImport`."""
863+
864+ layer = DatabaseFunctionalLayer
865+
866+ def setUp(self):
867+ # We have to be logged in to request imports
868+ TestCaseWithFactory.setUp(self, user='no-priv@canonical.com')
869+
870+ def test_requestsJob(self):
871+ code_import = self.factory.makeCodeImport(
872+ git_repo_url=self.factory.getUniqueURL())
873+ requester = self.factory.makePerson()
874+ old_date = code_import.import_job.date_due
875+ code_import.requestImport(requester)
876+ self.assertEqual(requester, code_import.import_job.requesting_user)
877+ self.assertTrue(code_import.import_job.date_due <= old_date)
878+
879+ def test_noop_if_already_requested(self):
880+ code_import = self.factory.makeCodeImport(
881+ git_repo_url=self.factory.getUniqueURL())
882+ requester = self.factory.makePerson()
883+ code_import.requestImport(requester)
884+ old_date = code_import.import_job.date_due
885+ code_import.requestImport(requester)
886+ # The checks don't matter so much, it's more that we don't get
887+ # an exception.
888+ self.assertEqual(requester, code_import.import_job.requesting_user)
889+ self.assertEqual(old_date, code_import.import_job.date_due)
890+
891+ def test_optional_error_if_already_requested(self):
892+ code_import = self.factory.makeCodeImport(
893+ git_repo_url=self.factory.getUniqueURL())
894+ requester = self.factory.makePerson()
895+ code_import.requestImport(requester)
896+ old_date = code_import.import_job.date_due
897+ e = self.assertRaises(
898+ CodeImportAlreadyRequested, code_import.requestImport, requester,
899+ error_if_already_requested=True)
900+ self.assertEqual(requester, e.requesting_user)
901+
902+ def test_exception_on_disabled(self):
903+ # get an SVN request, which isn't reviewed by default
904+ code_import = self.factory.makeCodeImport(
905+ svn_branch_url=self.factory.getUniqueURL())
906+ requester = self.factory.makePerson()
907+ # which leads to an exception if we try and ask for an import
908+ self.assertRaises(
909+ CodeImportNotInReviewedState, code_import.requestImport,
910+ requester)
911+
912+ def test_exception_if_already_running(self):
913+ code_import = self.factory.makeCodeImport(
914+ git_repo_url=self.factory.getUniqueURL())
915+ code_import = make_running_import(factory=self.factory,
916+ code_import=code_import)
917+ requester = self.factory.makePerson()
918+ self.assertRaises(
919+ CodeImportAlreadyRunning, code_import.requestImport,
920+ requester)
921+
922+
923 def test_suite():
924 return unittest.TestLoader().loadTestsFromName(__name__)
925
926=== modified file 'lib/lp/code/stories/webservice/xx-code-import.txt'
927--- lib/lp/code/stories/webservice/xx-code-import.txt 2010-04-14 17:44:00 +0000
928+++ lib/lp/code/stories/webservice/xx-code-import.txt 2010-04-16 04:01:13 +0000
929@@ -17,9 +17,15 @@
930 >>> other_person = factory.makePerson(name='other-person')
931 >>> removeSecurityProxy(person).join(team)
932 >>> product = factory.makeProduct(name='scruff')
933+ >>> svn_branch_url = "http://svn.domain.com/source"
934 >>> code_import = removeSecurityProxy(factory.makeProductCodeImport(
935+<<<<<<< TREE
936 ... registrant=person, product=product, branch_name='import',
937 ... svn_branch_url="http://svn.domain.com/source"))
938+=======
939+ ... registrant=person, product=product, branch_name='import',
940+ ... svn_branch_url=svn_branch_url))
941+>>>>>>> MERGE-SOURCE
942 >>> no_import_branch = removeSecurityProxy(factory.makeProductBranch(
943 ... owner=person, product=product, name='no-import'))
944 >>> logout()
945@@ -58,171 +64,365 @@
946 >>> print representation['rcs_type']
947 Subversion via CSCVS
948 >>> print representation['url']
949- http://svn.domain.com/source
950- >>> print representation['cvs_root']
951- None
952- >>> print representation['cvs_module']
953- None
954- >>> print representation['date_last_successful']
955- None
956-
957-
958-Package Branches
959-----------------
960-
961-The same is true for package branches.
962-
963- >>> login(ANONYMOUS)
964- >>> distribution = factory.makeDistribution(name='scruffbuntu')
965- >>> distroseries = factory.makeDistroSeries(
966- ... name='manic', distribution=distribution)
967- >>> source_package = factory.makeSourcePackage(
968- ... sourcepackagename='scruff', distroseries=distroseries)
969- >>> code_import = removeSecurityProxy(factory.makePackageCodeImport(
970- ... registrant=person, sourcepackage=source_package,
971- ... branch_name='import',
972- ... svn_branch_url="http://svn.domain.com/package_source"))
973- >>> logout()
974-
975-There is a link on the branch object
976-
977- >>> branch_url = '/' + code_import.branch.unique_name
978- >>> response = import_webservice.get(branch_url)
979- >>> representation = response.jsonBody()
980- >>> print representation['code_import_link']
981- http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import
982-
983-and there is information available about the import itsef.
984-
985- >>> import_url = representation['code_import_link']
986- >>> response = import_webservice.get(import_url)
987- >>> representation = response.jsonBody()
988- >>> print representation['self_link'] == import_url
989- True
990- >>> print representation['branch_link']
991- http://.../~import-owner/scruffbuntu/manic/scruff/import
992- >>> print representation['review_status']
993- Pending Review
994- >>> print representation['rcs_type']
995- Subversion via CSCVS
996- >>> print representation['url']
997- http://svn.domain.com/package_source
998- >>> print representation['cvs_root']
999- None
1000- >>> print representation['cvs_module']
1001- None
1002- >>> print representation['date_last_successful']
1003- None
1004-
1005-
1006-== Creating Imports ==
1007-
1008-We can create an import using the API by calling a method on the project.
1009-
1010- >>> product_url = '/' + product.name
1011- >>> new_remote_url = factory.getUniqueURL()
1012- >>> response = import_webservice.named_post(product_url, 'newCodeImport',
1013- ... branch_name='new-import', rcs_type='Git',
1014- ... url=new_remote_url)
1015- >>> print response.status
1016- 201
1017- >>> location = response.getHeader('Location')
1018- >>> response = import_webservice.get(location)
1019- >>> representation = response.jsonBody()
1020- >>> print representation['self_link']
1021- http://.../~import-owner/scruff/new-import/+code-import
1022- >>> print representation['branch_link']
1023- http://.../~import-owner/scruff/new-import
1024- >>> print representation['rcs_type']
1025- Git
1026- >>> print representation['url'] == new_remote_url
1027- True
1028- >>> print representation['cvs_root']
1029- None
1030- >>> print representation['cvs_module']
1031- None
1032- >>> print representation['date_last_successful']
1033- None
1034-
1035-If we must we can create a CVS import.
1036-
1037- >>> product_url = '/' + product.name
1038- >>> new_remote_url = factory.getUniqueURL()
1039- >>> response = import_webservice.named_post(product_url, 'newCodeImport',
1040- ... branch_name='cvs-import', rcs_type='Concurrent Versions System',
1041- ... cvs_root=new_remote_url, cvs_module="foo")
1042- >>> print response.status
1043- 201
1044- >>> location = response.getHeader('Location')
1045- >>> response = import_webservice.get(location)
1046- >>> representation = response.jsonBody()
1047- >>> print representation['self_link']
1048- http://.../~import-owner/scruff/cvs-import/+code-import
1049- >>> print representation['branch_link']
1050- http://.../~import-owner/scruff/cvs-import
1051- >>> print representation['rcs_type']
1052- Concurrent Versions System
1053- >>> print representation['url']
1054- None
1055- >>> print representation['cvs_root'] == new_remote_url
1056- True
1057- >>> print representation['cvs_module'] == "foo"
1058- True
1059- >>> print representation['date_last_successful']
1060- None
1061-
1062-We can also create an import targetting a source package.
1063-
1064- >>> source_package_url = (
1065- ... '/' + distribution.name + '/' + distroseries.name + '/+source/'
1066- ... + source_package.name)
1067- >>> new_remote_url = factory.getUniqueURL()
1068- >>> response = import_webservice.named_post(source_package_url,
1069- ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial',
1070- ... url=new_remote_url)
1071- >>> print response.status
1072- 201
1073- >>> location = response.getHeader('Location')
1074- >>> response = import_webservice.get(location)
1075- >>> representation = response.jsonBody()
1076- >>> print representation['self_link']
1077- http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import
1078- >>> print representation['branch_link']
1079- http://.../~import-owner/scruffbuntu/manic/scruff/new-import
1080- >>> print representation['rcs_type']
1081- Mercurial
1082- >>> print representation['url'] == new_remote_url
1083- True
1084- >>> print representation['cvs_root']
1085- None
1086- >>> print representation['cvs_module']
1087- None
1088- >>> print representation['date_last_successful']
1089- None
1090-
1091-If we wish to create a branch owned by a team we are part of then we can.
1092-
1093- >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team')
1094- >>> new_remote_url = factory.getUniqueURL()
1095- >>> response = import_webservice.named_post(product_url, 'newCodeImport',
1096- ... branch_name='team-import', rcs_type='Git',
1097- ... url=new_remote_url, owner=team_url)
1098- >>> print response.status
1099- 201
1100- >>> location = response.getHeader('Location')
1101- >>> response = import_webservice.get(location)
1102- >>> representation = response.jsonBody()
1103- >>> print representation['self_link']
1104- http://.../~import-owner-team/scruff/team-import/+code-import
1105- >>> print representation['branch_link']
1106- http://.../~import-owner-team/scruff/team-import
1107- >>> print representation['rcs_type']
1108- Git
1109- >>> print representation['url'] == new_remote_url
1110- True
1111- >>> print representation['cvs_root']
1112- None
1113- >>> print representation['cvs_module']
1114- None
1115- >>> print representation['date_last_successful']
1116- None
1117+<<<<<<< TREE
1118+ http://svn.domain.com/source
1119+ >>> print representation['cvs_root']
1120+ None
1121+ >>> print representation['cvs_module']
1122+ None
1123+ >>> print representation['date_last_successful']
1124+ None
1125+
1126+
1127+Package Branches
1128+----------------
1129+
1130+The same is true for package branches.
1131+
1132+ >>> login(ANONYMOUS)
1133+ >>> distribution = factory.makeDistribution(name='scruffbuntu')
1134+ >>> distroseries = factory.makeDistroSeries(
1135+ ... name='manic', distribution=distribution)
1136+ >>> source_package = factory.makeSourcePackage(
1137+ ... sourcepackagename='scruff', distroseries=distroseries)
1138+ >>> code_import = removeSecurityProxy(factory.makePackageCodeImport(
1139+ ... registrant=person, sourcepackage=source_package,
1140+ ... branch_name='import',
1141+ ... svn_branch_url="http://svn.domain.com/package_source"))
1142+ >>> logout()
1143+
1144+There is a link on the branch object
1145+
1146+ >>> branch_url = '/' + code_import.branch.unique_name
1147+ >>> response = import_webservice.get(branch_url)
1148+ >>> representation = response.jsonBody()
1149+ >>> print representation['code_import_link']
1150+ http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import
1151+
1152+and there is information available about the import itsef.
1153+
1154+ >>> import_url = representation['code_import_link']
1155+ >>> response = import_webservice.get(import_url)
1156+ >>> representation = response.jsonBody()
1157+ >>> print representation['self_link'] == import_url
1158+ True
1159+ >>> print representation['branch_link']
1160+ http://.../~import-owner/scruffbuntu/manic/scruff/import
1161+ >>> print representation['review_status']
1162+ Pending Review
1163+ >>> print representation['rcs_type']
1164+ Subversion via CSCVS
1165+ >>> print representation['url']
1166+ http://svn.domain.com/package_source
1167+ >>> print representation['cvs_root']
1168+ None
1169+ >>> print representation['cvs_module']
1170+ None
1171+ >>> print representation['date_last_successful']
1172+ None
1173+
1174+
1175+== Creating Imports ==
1176+
1177+We can create an import using the API by calling a method on the project.
1178+
1179+ >>> product_url = '/' + product.name
1180+ >>> new_remote_url = factory.getUniqueURL()
1181+ >>> response = import_webservice.named_post(product_url, 'newCodeImport',
1182+ ... branch_name='new-import', rcs_type='Git',
1183+ ... url=new_remote_url)
1184+ >>> print response.status
1185+ 201
1186+ >>> location = response.getHeader('Location')
1187+ >>> response = import_webservice.get(location)
1188+ >>> representation = response.jsonBody()
1189+ >>> print representation['self_link']
1190+ http://.../~import-owner/scruff/new-import/+code-import
1191+ >>> print representation['branch_link']
1192+ http://.../~import-owner/scruff/new-import
1193+ >>> print representation['rcs_type']
1194+ Git
1195+ >>> print representation['url'] == new_remote_url
1196+ True
1197+ >>> print representation['cvs_root']
1198+ None
1199+ >>> print representation['cvs_module']
1200+ None
1201+ >>> print representation['date_last_successful']
1202+ None
1203+
1204+If we must we can create a CVS import.
1205+
1206+ >>> product_url = '/' + product.name
1207+ >>> new_remote_url = factory.getUniqueURL()
1208+ >>> response = import_webservice.named_post(product_url, 'newCodeImport',
1209+ ... branch_name='cvs-import', rcs_type='Concurrent Versions System',
1210+ ... cvs_root=new_remote_url, cvs_module="foo")
1211+ >>> print response.status
1212+ 201
1213+ >>> location = response.getHeader('Location')
1214+ >>> response = import_webservice.get(location)
1215+ >>> representation = response.jsonBody()
1216+ >>> print representation['self_link']
1217+ http://.../~import-owner/scruff/cvs-import/+code-import
1218+ >>> print representation['branch_link']
1219+ http://.../~import-owner/scruff/cvs-import
1220+ >>> print representation['rcs_type']
1221+ Concurrent Versions System
1222+ >>> print representation['url']
1223+ None
1224+ >>> print representation['cvs_root'] == new_remote_url
1225+ True
1226+ >>> print representation['cvs_module'] == "foo"
1227+ True
1228+ >>> print representation['date_last_successful']
1229+ None
1230+
1231+We can also create an import targetting a source package.
1232+
1233+ >>> source_package_url = (
1234+ ... '/' + distribution.name + '/' + distroseries.name + '/+source/'
1235+ ... + source_package.name)
1236+ >>> new_remote_url = factory.getUniqueURL()
1237+ >>> response = import_webservice.named_post(source_package_url,
1238+ ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial',
1239+ ... url=new_remote_url)
1240+ >>> print response.status
1241+ 201
1242+ >>> location = response.getHeader('Location')
1243+ >>> response = import_webservice.get(location)
1244+ >>> representation = response.jsonBody()
1245+ >>> print representation['self_link']
1246+ http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import
1247+ >>> print representation['branch_link']
1248+ http://.../~import-owner/scruffbuntu/manic/scruff/new-import
1249+ >>> print representation['rcs_type']
1250+ Mercurial
1251+ >>> print representation['url'] == new_remote_url
1252+ True
1253+ >>> print representation['cvs_root']
1254+ None
1255+ >>> print representation['cvs_module']
1256+ None
1257+ >>> print representation['date_last_successful']
1258+ None
1259+
1260+If we wish to create a branch owned by a team we are part of then we can.
1261+
1262+ >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team')
1263+ >>> new_remote_url = factory.getUniqueURL()
1264+ >>> response = import_webservice.named_post(product_url, 'newCodeImport',
1265+ ... branch_name='team-import', rcs_type='Git',
1266+ ... url=new_remote_url, owner=team_url)
1267+ >>> print response.status
1268+ 201
1269+ >>> location = response.getHeader('Location')
1270+ >>> response = import_webservice.get(location)
1271+ >>> representation = response.jsonBody()
1272+ >>> print representation['self_link']
1273+ http://.../~import-owner-team/scruff/team-import/+code-import
1274+ >>> print representation['branch_link']
1275+ http://.../~import-owner-team/scruff/team-import
1276+ >>> print representation['rcs_type']
1277+ Git
1278+ >>> print representation['url'] == new_remote_url
1279+ True
1280+ >>> print representation['cvs_root']
1281+ None
1282+ >>> print representation['cvs_module']
1283+ None
1284+ >>> print representation['date_last_successful']
1285+ None
1286+=======
1287+ http://svn.domain.com/source
1288+ >>> print representation['cvs_root']
1289+ None
1290+ >>> print representation['cvs_module']
1291+ None
1292+ >>> print representation['date_last_successful']
1293+ None
1294+
1295+
1296+Package Branches
1297+----------------
1298+
1299+The same is true for package branches.
1300+
1301+ >>> login(ANONYMOUS)
1302+ >>> distribution = factory.makeDistribution(name='scruffbuntu')
1303+ >>> distroseries = factory.makeDistroSeries(
1304+ ... name='manic', distribution=distribution)
1305+ >>> source_package = factory.makeSourcePackage(
1306+ ... sourcepackagename='scruff', distroseries=distroseries)
1307+ >>> code_import = removeSecurityProxy(factory.makePackageCodeImport(
1308+ ... registrant=person, sourcepackage=source_package,
1309+ ... branch_name='import',
1310+ ... svn_branch_url="http://svn.domain.com/package_source"))
1311+ >>> logout()
1312+ >>> import_webservice = webservice_for_person(
1313+ ... person, permission=OAuthPermission.WRITE_PUBLIC)
1314+
1315+There is a link on the branch object
1316+
1317+ >>> branch_url = '/' + code_import.branch.unique_name
1318+ >>> response = import_webservice.get(branch_url)
1319+ >>> representation = response.jsonBody()
1320+ >>> print representation['code_import_link']
1321+ http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import
1322+
1323+and there is information available about the import itsef.
1324+
1325+ >>> import_url = representation['code_import_link']
1326+ >>> response = import_webservice.get(import_url)
1327+ >>> representation = response.jsonBody()
1328+ >>> print representation['self_link'] == import_url
1329+ True
1330+ >>> print representation['branch_link']
1331+ http://.../~import-owner/scruffbuntu/manic/scruff/import
1332+ >>> print representation['review_status']
1333+ Pending Review
1334+ >>> print representation['rcs_type']
1335+ Subversion via CSCVS
1336+ >>> print representation['url']
1337+ http://svn.domain.com/package_source
1338+ >>> print representation['cvs_root']
1339+ None
1340+ >>> print representation['cvs_module']
1341+ None
1342+ >>> print representation['date_last_successful']
1343+ None
1344+
1345+== Creating Imports ==
1346+
1347+We can create an import using the API by calling a method on the project.
1348+
1349+ >>> product_url = '/' + product.name
1350+ >>> new_remote_url = factory.getUniqueURL()
1351+ >>> response = import_webservice.named_post(product_url, 'newCodeImport',
1352+ ... branch_name='new-import', rcs_type='Git',
1353+ ... url=new_remote_url)
1354+ >>> print response.status
1355+ 201
1356+ >>> location = response.getHeader('Location')
1357+ >>> response = import_webservice.get(location)
1358+ >>> representation = response.jsonBody()
1359+ >>> print representation['self_link']
1360+ http://.../~import-owner/scruff/new-import/+code-import
1361+ >>> print representation['branch_link']
1362+ http://.../~import-owner/scruff/new-import
1363+ >>> print representation['rcs_type']
1364+ Git
1365+ >>> print representation['url'] == new_remote_url
1366+ True
1367+ >>> print representation['cvs_root']
1368+ None
1369+ >>> print representation['cvs_module']
1370+ None
1371+ >>> print representation['date_last_successful']
1372+ None
1373+
1374+If we must we can create a CVS import.
1375+
1376+ >>> product_url = '/' + product.name
1377+ >>> new_remote_url = factory.getUniqueURL()
1378+ >>> response = import_webservice.named_post(product_url, 'newCodeImport',
1379+ ... branch_name='cvs-import', rcs_type='Concurrent Versions System',
1380+ ... cvs_root=new_remote_url, cvs_module="foo")
1381+ >>> print response.status
1382+ 201
1383+ >>> location = response.getHeader('Location')
1384+ >>> response = import_webservice.get(location)
1385+ >>> representation = response.jsonBody()
1386+ >>> print representation['self_link']
1387+ http://.../~import-owner/scruff/cvs-import/+code-import
1388+ >>> print representation['branch_link']
1389+ http://.../~import-owner/scruff/cvs-import
1390+ >>> print representation['rcs_type']
1391+ Concurrent Versions System
1392+ >>> print representation['url']
1393+ None
1394+ >>> print representation['cvs_root'] == new_remote_url
1395+ True
1396+ >>> print representation['cvs_module'] == "foo"
1397+ True
1398+ >>> print representation['date_last_successful']
1399+ None
1400+
1401+We can also create an import targetting a source package.
1402+
1403+ >>> source_package_url = (
1404+ ... '/' + distribution.name + '/' + distroseries.name + '/+source/'
1405+ ... + source_package.name)
1406+ >>> new_remote_url = factory.getUniqueURL()
1407+ >>> response = import_webservice.named_post(source_package_url,
1408+ ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial',
1409+ ... url=new_remote_url)
1410+ >>> print response.status
1411+ 201
1412+ >>> location = response.getHeader('Location')
1413+ >>> response = import_webservice.get(location)
1414+ >>> representation = response.jsonBody()
1415+ >>> print representation['self_link']
1416+ http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import
1417+ >>> print representation['branch_link']
1418+ http://.../~import-owner/scruffbuntu/manic/scruff/new-import
1419+ >>> print representation['rcs_type']
1420+ Mercurial
1421+ >>> print representation['url'] == new_remote_url
1422+ True
1423+ >>> print representation['cvs_root']
1424+ None
1425+ >>> print representation['cvs_module']
1426+ None
1427+ >>> print representation['date_last_successful']
1428+ None
1429+
1430+If we wish to create a branch owned by a team we are part of then we can.
1431+
1432+ >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team')
1433+ >>> new_remote_url = factory.getUniqueURL()
1434+ >>> response = import_webservice.named_post(product_url, 'newCodeImport',
1435+ ... branch_name='team-import', rcs_type='Git',
1436+ ... url=new_remote_url, owner=team_url)
1437+ >>> print response.status
1438+ 201
1439+ >>> location = response.getHeader('Location')
1440+ >>> response = import_webservice.get(location)
1441+ >>> representation = response.jsonBody()
1442+ >>> print representation['self_link']
1443+ http://.../~import-owner-team/scruff/team-import/+code-import
1444+ >>> print representation['branch_link']
1445+ http://.../~import-owner-team/scruff/team-import
1446+ >>> print representation['rcs_type']
1447+ Git
1448+ >>> print representation['url'] == new_remote_url
1449+ True
1450+ >>> print representation['cvs_root']
1451+ None
1452+ >>> print representation['cvs_module']
1453+ None
1454+ >>> print representation['date_last_successful']
1455+ None
1456+
1457+
1458+== Requesting an Import ==
1459+
1460+You can request that an approved, working import happen soon over the
1461+API using the requestImport() method.
1462+
1463+ >>> login(ANONYMOUS)
1464+ >>> git_import = factory.makeProductCodeImport(
1465+ ... registrant=person, product=product, branch_name='git-import',
1466+ ... git_repo_url=factory.getUniqueURL())
1467+ >>> git_import_url = (
1468+ ... '/' + git_import.branch.unique_name + '/+code-import')
1469+ >>> logout()
1470+ >>> import_webservice = webservice_for_person(
1471+ ... person, permission=OAuthPermission.WRITE_PUBLIC)
1472+ >>> response = import_webservice.named_post(
1473+ ... git_import_url, 'requestImport')
1474+ >>> print response.status
1475+ 200
1476+ >>> print response.jsonBody()
1477+ None
1478+>>>>>>> MERGE-SOURCE
1479
1480=== modified file 'lib/lp/services/worlddata/doc/language.txt'
1481--- lib/lp/services/worlddata/doc/language.txt 2010-02-17 10:39:16 +0000
1482+++ lib/lp/services/worlddata/doc/language.txt 2010-04-16 04:01:13 +0000
1483@@ -260,8 +260,40 @@
1484 Serbian ("Latn" variant)
1485
1486
1487+translators
1488+===========
1489+
1490+Property `translators` contains the list of `Person`s who are considered
1491+translators for this language.
1492+
1493+ >>> sr = language_set.getLanguageByCode('sr')
1494+ >>> list(sr.translators)
1495+ []
1496+
1497+To be considered a translator, they must have done some translations and
1498+have the language among their preferred languages.
1499+
1500+ >>> translator = factory.makePerson(name=u'serbian-translator')
1501+ >>> translator.addLanguage(sr)
1502+ >>> from canonical.testing import LaunchpadZopelessLayer
1503+ >>> LaunchpadZopelessLayer.commit()
1504+
1505+ # We need to fake some Karma.
1506+ >>> from lp.registry.model.karma import KarmaCategory, KarmaCache
1507+ >>> LaunchpadZopelessLayer.switchDbUser('karma')
1508+ >>> translations_category = KarmaCategory.selectOne(
1509+ ... KarmaCategory.name=='translations')
1510+ >>> karma = KarmaCache(person=translator,
1511+ ... category=translations_category,
1512+ ... karmavalue=1)
1513+ >>> LaunchpadZopelessLayer.commit()
1514+ >>> LaunchpadZopelessLayer.switchDbUser('launchpad')
1515+ >>> [translator.name for translator in sr.translators]
1516+ [u'serbian-translator']
1517+
1518+
1519 =========
1520-countries
1521+Countries
1522 =========
1523
1524 Property holding a list of countries a language is spoken in, and allowing
1525
1526=== modified file 'lib/lp/services/worlddata/interfaces/language.py'
1527--- lib/lp/services/worlddata/interfaces/language.py 2010-03-05 14:02:05 +0000
1528+++ lib/lp/services/worlddata/interfaces/language.py 2010-04-16 04:01:13 +0000
1529@@ -16,6 +16,7 @@
1530 from zope.schema import TextLine, Int, Choice, Bool, Field, Set
1531 from zope.interface import Interface, Attribute
1532 from lazr.enum import DBEnumeratedType, DBItem
1533+from lazr.lifecycle.snapshot import doNotSnapshot
1534
1535 from lazr.restful.declarations import (
1536 collection_default_content, exported, export_as_webservice_collection,
1537@@ -75,9 +76,9 @@
1538 required=False),
1539 exported_as='plural_expression')
1540
1541- translators = Field(
1542+ translators = doNotSnapshot(Field(
1543 title=u'List of Person/Team that translate into this language.',
1544- required=True)
1545+ required=True))
1546
1547 translators_count = exported(
1548 Int(
1549
1550=== modified file 'lib/lp/services/worlddata/tests/test_doc.py'
1551--- lib/lp/services/worlddata/tests/test_doc.py 2009-06-30 16:56:07 +0000
1552+++ lib/lp/services/worlddata/tests/test_doc.py 2010-04-16 04:01:13 +0000
1553@@ -6,9 +6,20 @@
1554 """
1555
1556 import os
1557+
1558+from canonical.launchpad.testing.systemdocs import (
1559+ LayeredDocFileSuite, setUp, tearDown)
1560+from canonical.testing import LaunchpadZopelessLayer
1561+
1562 from lp.services.testing import build_test_suite
1563
1564 here = os.path.dirname(os.path.realpath(__file__))
1565+special = {
1566+ 'language.txt': LayeredDocFileSuite(
1567+ '../doc/language.txt',
1568+ layer=LaunchpadZopelessLayer,
1569+ setUp=setUp, tearDown=tearDown),
1570+ }
1571
1572 def test_suite():
1573- return build_test_suite(here)
1574+ return build_test_suite(here, special)
1575
1576=== added file 'lib/lp/services/worlddata/tests/test_language.py'
1577--- lib/lp/services/worlddata/tests/test_language.py 1970-01-01 00:00:00 +0000
1578+++ lib/lp/services/worlddata/tests/test_language.py 2010-04-16 04:01:13 +0000
1579@@ -0,0 +1,21 @@
1580+# Copyright 2010 Canonical Ltd. This software is licensed under the
1581+# GNU Affero General Public License version 3 (see the file LICENSE).
1582+
1583+__metaclass__ = type
1584+
1585+from canonical.testing import FunctionalLayer
1586+from lazr.lifecycle.interfaces import IDoNotSnapshot
1587+from lp.services.worlddata.interfaces.language import ILanguage
1588+from lp.testing import TestCaseWithFactory
1589+
1590+
1591+class TestLanguageWebservice(TestCaseWithFactory):
1592+ """Test Language web service API."""
1593+
1594+ layer = FunctionalLayer
1595+
1596+ def test_translators(self):
1597+ self.failUnless(
1598+ IDoNotSnapshot.providedBy(ILanguage['translators']),
1599+ "ILanguage.translators should not be included in snapshots, "
1600+ "see bug 553093.")
1601
1602=== modified file 'lib/lp/soyuz/doc/archive.txt'
1603--- lib/lp/soyuz/doc/archive.txt 2010-04-08 02:35:06 +0000
1604+++ lib/lp/soyuz/doc/archive.txt 2010-04-16 04:01:13 +0000
1605@@ -1407,10 +1407,10 @@
1606 ppa
1607
1608 We can take the opportunity to check if the default 'authorized_size'
1609-corresponds to what we state in our policy, 1024 MiB:
1610+corresponds to what we state in our policy, 2048 MiB:
1611
1612 >>> name16.archive.authorized_size
1613- 1024
1614+ 2048
1615
1616 An archive is also associated with a distribution. This can be found on
1617 the distribution property. The default distribution is "ubuntu":
1618
1619=== modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt'
1620=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-translations.txt'
1621--- lib/lp/soyuz/doc/distroseriesqueue-translations.txt 2010-03-12 13:39:33 +0000
1622+++ lib/lp/soyuz/doc/distroseriesqueue-translations.txt 2010-04-16 04:01:13 +0000
1623@@ -297,8 +297,35 @@
1624
1625 >>> queue_item.customfiles[0].publish(mock_logger)
1626 DEBUG: Publishing custom pmount, pmount_0.9.7-2ubuntu2_amd64_translations.tar.gz to ubuntu/dapper
1627- DEBUG: Skipping translations since it is a PPA.
1628-
1629+ DEBUG: Skipping translations since its purpose is not in
1630+ MAIN_ARCHIVE_PURPOSES.
1631+
1632+ # And this time, we see that there are no entries imported in the queue.
1633+ >>> translation_import_queue.getAllEntries(target=ubuntu).count()
1634+ 0
1635+ >>> transaction.abort()
1636+
1637+
1638+== Translations from a rebuild ==
1639+
1640+Translations coming from rebuilt packages are also ignored.
1641+
1642+ >>> from lp.registry.interfaces.person import IPersonSet
1643+ >>> from lp.soyuz.interfaces.archive import ArchivePurpose, IArchiveSet
1644+
1645+ >>> foobar_archive = getUtility(IArchiveSet).new(
1646+ ... purpose=ArchivePurpose.COPY,
1647+ ... owner=getUtility(IPersonSet).getByName('name16'),
1648+ ... name='rebuilds')
1649+
1650+ >>> dapper = getUtility(IDistributionSet)['ubuntu']['dapper']
1651+ >>> queue_item = dapper.getQueueItems(status=PackageUploadStatus.NEW)[0]
1652+ >>> queue_item.archive = foobar_archive
1653+
1654+ >>> queue_item.customfiles[0].publish(mock_logger)
1655+ DEBUG: Publishing custom pmount, pmount_0.9.7-2ubuntu2_amd64_translations.tar.gz to ubuntu/dapper
1656+ DEBUG: Skipping translations since its purpose is not in
1657+ MAIN_ARCHIVE_PURPOSES.
1658
1659 # And this time, we see that there are no entries imported in the queue.
1660 >>> translation_import_queue.getAllEntries(target=ubuntu).count()
1661@@ -314,6 +341,8 @@
1662 >>> from zope.interface import implements
1663 >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
1664 >>> from lp.soyuz.model.queue import PackageUploadCustom
1665+ >>> from lp.soyuz.interfaces.archive import (
1666+ ... IArchive, ArchivePurpose)
1667 >>> from lp.soyuz.interfaces.queue import (
1668 ... IPackageUpload, IPackageUploadCustom)
1669 >>> from lp.soyuz.interfaces.queue import PackageUploadCustomFormat
1670@@ -323,6 +352,11 @@
1671 ... ISourcePackageRelease)
1672 >>> from lp.registry.interfaces.pocket import PackagePublishingPocket
1673
1674+ >>> class MockArchive:
1675+ ... implements(IArchive)
1676+ ... def __init__(self, purpose):
1677+ ... self.purpose = purpose
1678+
1679 >>> class MockSourcePackageRelease:
1680 ... implements(ISourcePackageRelease)
1681 ... def __init__(self, component, creator):
1682@@ -338,14 +372,13 @@
1683
1684 >>> class MockPackageUpload:
1685 ... implements(IPackageUpload)
1686- ... def __init__(self, pocket, auto_sync, sourcepackagerelease):
1687+ ... def __init__(self, pocket, auto_sync, sourcepackagerelease,
1688+ ... archive):
1689 ... self.id = 1
1690 ... self.pocket = pocket
1691 ... self.auto_sync = auto_sync
1692 ... self.sourcepackagerelease = sourcepackagerelease
1693- ...
1694- ... def isPPA(self):
1695- ... return False
1696+ ... self.archive = archive
1697 ...
1698 ... def isAutoSyncUpload(self, changed_by_email=None):
1699 ... return self.auto_sync
1700@@ -363,10 +396,11 @@
1701
1702 >>> katie = getUtility(ILaunchpadCelebrities).katie
1703 >>> release_pocket = PackagePublishingPocket.RELEASE
1704+ >>> archive = MockArchive(ArchivePurpose.PRIMARY)
1705
1706 >>> katie_sourcepackagerelease = MockSourcePackageRelease('main', katie)
1707 >>> sync_package_upload = MockPackageUpload(
1708- ... release_pocket, True, katie_sourcepackagerelease)
1709+ ... release_pocket, True, katie_sourcepackagerelease, archive)
1710 >>> sync_package_upload.isAutoSyncUpload()
1711 True
1712 >>> translations_upload = MockPackageUploadCustom()
1713@@ -377,7 +411,7 @@
1714 Non-auto-sync uploads by 'katie' still indicate 'katie' as the uploader.
1715
1716 >>> non_sync_package_upload = MockPackageUpload(
1717- ... release_pocket, False, katie_sourcepackagerelease)
1718+ ... release_pocket, False, katie_sourcepackagerelease, archive)
1719 >>> non_sync_package_upload.isAutoSyncUpload()
1720 False
1721 >>> translations_upload.packageupload = non_sync_package_upload
1722@@ -390,7 +424,7 @@
1723 >>> carlos = person_set.getByName('carlos')
1724 >>> carlos_sourcepackagerelease = MockSourcePackageRelease('main', carlos)
1725 >>> carlos_package_upload = MockPackageUpload(
1726- ... release_pocket, False, carlos_sourcepackagerelease)
1727+ ... release_pocket, False, carlos_sourcepackagerelease, archive)
1728 >>> carlos_package_upload.isAutoSyncUpload()
1729 False
1730 >>> translations_upload.packageupload = carlos_package_upload
1731
1732=== modified file 'lib/lp/soyuz/doc/gina.txt'
1733=== modified file 'lib/lp/soyuz/model/archive.py'
1734--- lib/lp/soyuz/model/archive.py 2010-04-12 08:29:02 +0000
1735+++ lib/lp/soyuz/model/archive.py 2010-04-16 04:01:13 +0000
1736@@ -164,7 +164,7 @@
1737 dbName='require_virtualized', notNull=True, default=True)
1738
1739 authorized_size = IntCol(
1740- dbName='authorized_size', notNull=False, default=1024)
1741+ dbName='authorized_size', notNull=False, default=2048)
1742
1743 sources_cached = IntCol(
1744 dbName='sources_cached', notNull=False, default=0)
1745
1746=== modified file 'lib/lp/soyuz/model/queue.py'
1747--- lib/lp/soyuz/model/queue.py 2010-04-12 11:37:48 +0000
1748+++ lib/lp/soyuz/model/queue.py 2010-04-16 04:01:13 +0000
1749@@ -43,6 +43,7 @@
1750 from canonical.database.sqlbase import SQLBase, sqlvalues
1751 from canonical.encoding import guess as guess_encoding, ascii_smash
1752 from canonical.launchpad.helpers import get_email_template
1753+from lp.soyuz.interfaces.archive import MAIN_ARCHIVE_PURPOSES
1754 from lp.soyuz.interfaces.binarypackagerelease import (
1755 BinaryPackageFormat)
1756 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
1757@@ -1701,9 +1702,11 @@
1758 """See `IPackageUploadCustom`."""
1759 sourcepackagerelease = self.packageupload.sourcepackagerelease
1760
1761- # Ignore translation coming from PPA.
1762- if self.packageupload.isPPA():
1763- debug(logger, "Skipping translations since it is a PPA.")
1764+ # Ignore translations not with main distribution purposes.
1765+ if self.packageupload.archive.purpose not in MAIN_ARCHIVE_PURPOSES:
1766+ debug(logger,
1767+ "Skipping translations since its purpose is not "
1768+ "in MAIN_ARCHIVE_PURPOSES.")
1769 return
1770
1771 valid_pockets = (
1772
1773=== modified file 'lib/lp/testing/factory.py'
1774=== modified file 'lib/lp/translations/templates/language-index.pt'
1775--- lib/lp/translations/templates/language-index.pt 2009-11-27 14:18:05 +0000
1776+++ lib/lp/translations/templates/language-index.pt 2010-04-16 04:01:13 +0000
1777@@ -66,36 +66,17 @@
1778 being experts in
1779 <tal:language replace="view/language_name">
1780 Espa&ntilde;ol
1781- </tal:language>
1782- :
1783+ </tal:language>:
1784 </p>
1785- <table>
1786- <tr tal:repeat="expert_info view/translation_teams">
1787- <td>
1788- <img tal:condition="expert_info/expert/isTeam"
1789- src="/@@/team" />
1790- <img tal:condition="not:expert_info/expert/isTeam"
1791- src="/@@/person" />
1792- </td>
1793- <td>
1794- <div>
1795- <a
1796- tal:attributes="
1797- href expert_info/expert/fmt:url;
1798- title expert_info/expert/displayname/fmt:shorten/80"
1799- tal:content="expert_info/expert/displayname"
1800- >Expert name</a>(
1801- <tal:groups repeat="group expert_info/groups">
1802- <a tal:attributes="
1803- href group/fmt:url;
1804- title group/title/fmt:shorten/80"
1805- tal:content="group/title"
1806- >Translation group title</a>
1807- </tal:groups>)
1808- </div>
1809- </td>
1810- </tr>
1811- </table>
1812+ <div tal:repeat="expert_info view/translation_teams">
1813+ <a tal:replace="structure expert_info/expert/fmt:link">Person</a>
1814+ (<tal:groups repeat="group expert_info/groups"
1815+ ><a tal:replace="structure group/fmt:link"
1816+ >Translation group title</a
1817+ ><tal:comma
1818+ condition="not:repeat/group/end">, </tal:comma
1819+ ></tal:groups>)
1820+ </div>
1821 <p tal:condition="not:view/translation_teams">
1822 <tal:language replace="view/language_name">
1823 Espa&ntilde;ol

Subscribers

People subscribed via source and target branches

to status/vote changes: