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
=== modified file 'Makefile'
=== modified file 'configs/testrunner/launchpad-lazr.conf'
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2010-04-13 20:23:15 +0000
+++ database/schema/security.cfg 2010-04-16 04:01:13 +0000
@@ -1263,6 +1263,7 @@
1263public.question = SELECT1263public.question = SELECT
1264public.questionbug = SELECT1264public.questionbug = SELECT
1265public.distribution = SELECT1265public.distribution = SELECT
1266public.distributionsourcepackage = SELECT, INSERT, UPDATE
1266public.distroseries = SELECT1267public.distroseries = SELECT
1267public.sourcepackagename = SELECT1268public.sourcepackagename = SELECT
1268public.sourcepackagerelease = SELECT1269public.sourcepackagerelease = SELECT
12691270
=== modified file 'lib/canonical/launchpad/doc/security-teams.txt'
--- lib/canonical/launchpad/doc/security-teams.txt 2009-08-14 12:59:56 +0000
+++ lib/canonical/launchpad/doc/security-teams.txt 2010-04-16 04:01:13 +0000
@@ -268,7 +268,8 @@
268 >>> bug.addTask(owner=reporter, target=distribution)268 >>> bug.addTask(owner=reporter, target=distribution)
269 <BugTask at ...>269 <BugTask at ...>
270 >>> old_state = Snapshot(bug, providing=IBug)270 >>> old_state = Snapshot(bug, providing=IBug)
271 >>> bug.security_related = True271 >>> bug.setSecurityRelated(True)
272 True
272 >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related']))273 >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related']))
273 >>> for subscriber_name in sorted(274 >>> for subscriber_name in sorted(
274 ... s.displayname for s in bug.getDirectSubscribers()):275 ... s.displayname for s in bug.getDirectSubscribers()):
275276
=== modified file 'lib/canonical/launchpad/mail/commands.py'
--- lib/canonical/launchpad/mail/commands.py 2010-02-17 11:13:06 +0000
+++ lib/canonical/launchpad/mail/commands.py 2010-04-16 04:01:13 +0000
@@ -279,7 +279,7 @@
279 edited = True279 edited = True
280 edited_fields.add('private')280 edited_fields.add('private')
281 if context.security_related != security_related:281 if context.security_related != security_related:
282 context.security_related = security_related282 context.setSecurityRelated(security_related)
283 edited = True283 edited = True
284 edited_fields.add('security_related')284 edited_fields.add('security_related')
285285
286286
=== modified file 'lib/lp/archiveuploader/tests/test_ppauploadprocessor.py'
--- lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-04-12 15:02:30 +0000
+++ lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-04-16 04:01:13 +0000
@@ -1252,9 +1252,9 @@
1252 the size of the upload plus the current PPA size must be smaller1252 the size of the upload plus the current PPA size must be smaller
1253 than the PPA.authorized_size, otherwise the upload will be rejected.1253 than the PPA.authorized_size, otherwise the upload will be rejected.
1254 """1254 """
1255 # Stuff 1024 MiB in name16 PPA, so anything will be above the1255 # Stuff 2048 MiB in name16 PPA, so anything will be above the
1256 # default quota limit, 1024 MiB.1256 # default quota limit, 2048 MiB.
1257 self._fillArchive(self.name16.archive, 1024 * (2 ** 20))1257 self._fillArchive(self.name16.archive, 2048 * (2 ** 20))
12581258
1259 upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu")1259 upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu")
1260 upload_results = self.processUpload(self.uploadprocessor, upload_dir)1260 upload_results = self.processUpload(self.uploadprocessor, upload_dir)
@@ -1267,7 +1267,7 @@
1267 contents = [1267 contents = [
1268 "Subject: bar_1.0-1_source.changes rejected",1268 "Subject: bar_1.0-1_source.changes rejected",
1269 "Rejected:",1269 "Rejected:",
1270 "PPA exceeded its size limit (1024.00 of 1024.00 MiB). "1270 "PPA exceeded its size limit (2048.00 of 2048.00 MiB). "
1271 "Ask a question in https://answers.launchpad.net/soyuz/ "1271 "Ask a question in https://answers.launchpad.net/soyuz/ "
1272 "if you need more space."]1272 "if you need more space."]
1273 self.assertEmail(contents)1273 self.assertEmail(contents)
@@ -1278,9 +1278,9 @@
1278 The system start warning users for uploads exceeding 95 % of1278 The system start warning users for uploads exceeding 95 % of
1279 the current size limit.1279 the current size limit.
1280 """1280 """
1281 # Stuff 973 MiB into name16 PPA, approximately 95 % of1281 # Stuff 1945 MiB into name16 PPA, approximately 95 % of
1282 # the default quota limit, 1024 MiB.1282 # the default quota limit, 2048 MiB.
1283 self._fillArchive(self.name16.archive, 973 * (2 ** 20))1283 self._fillArchive(self.name16.archive, 2000 * (2 ** 20))
12841284
1285 # Ensure the warning is sent in the acceptance notification.1285 # Ensure the warning is sent in the acceptance notification.
1286 upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu")1286 upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu")
@@ -1288,7 +1288,7 @@
1288 contents = [1288 contents = [
1289 "Subject: [PPA name16] [ubuntu/breezy] bar 1.0-1 (Accepted)",1289 "Subject: [PPA name16] [ubuntu/breezy] bar 1.0-1 (Accepted)",
1290 "Upload Warnings:",1290 "Upload Warnings:",
1291 "PPA exceeded 95 % of its size limit (973.00 of 1024.00 MiB). "1291 "PPA exceeded 95 % of its size limit (2000.00 of 2048.00 MiB). "
1292 "Ask a question in https://answers.launchpad.net/soyuz/ "1292 "Ask a question in https://answers.launchpad.net/soyuz/ "
1293 "if you need more space."]1293 "if you need more space."]
1294 self.assertEmail(contents)1294 self.assertEmail(contents)
12951295
=== modified file 'lib/lp/bugs/browser/bug.py'
--- lib/lp/bugs/browser/bug.py 2010-04-07 11:28:32 +0000
+++ lib/lp/bugs/browser/bug.py 2010-04-16 04:01:13 +0000
@@ -673,7 +673,7 @@
673 page_title = label673 page_title = label
674674
675 def setUpFields(self):675 def setUpFields(self):
676 """Make the read-only version of `private` writable."""676 """Make the read-only version of the form fields writable."""
677 private_field = Bool(677 private_field = Bool(
678 __name__='private',678 __name__='private',
679 title=_("This bug report should be private"),679 title=_("This bug report should be private"),
@@ -681,10 +681,17 @@
681 description=_("Private bug reports are visible only to "681 description=_("Private bug reports are visible only to "
682 "their subscribers."),682 "their subscribers."),
683 default=False)683 default=False)
684 security_related_field = Bool(
685 __name__='security_related',
686 title=_("This bug is a security vulnerability"),
687 required=False, default=False)
688
684 super(BugSecrecyEditView, self).setUpFields()689 super(BugSecrecyEditView, self).setUpFields()
685 self.form_fields = self.form_fields.omit('private')690 self.form_fields = self.form_fields.omit('private')
691 self.form_fields = self.form_fields.omit('security_related')
686 self.form_fields = (692 self.form_fields = (
687 formlib.form.Fields(private_field) + self.form_fields)693 formlib.form.Fields(private_field) +
694 formlib.form.Fields(security_related_field))
688695
689 @property696 @property
690 def initial_values(self):697 def initial_values(self):
@@ -705,16 +712,18 @@
705 bug_before_modification = Snapshot(712 bug_before_modification = Snapshot(
706 bug, providing=providedBy(bug))713 bug, providing=providedBy(bug))
707 private = data.pop('private')714 private = data.pop('private')
715 security_related = data.pop('security_related')
708 private_changed = bug.setPrivate(716 private_changed = bug.setPrivate(
709 private, getUtility(ILaunchBag).user)717 private, getUtility(ILaunchBag).user)
710 if private_changed:718 security_related_changed = bug.setSecurityRelated(security_related)
711 # Although the call to updateBugFromData later on will719 if private_changed or security_related_changed:
712 # send notification of changes, it will only do so if it720 changed_fields = []
713 # makes the change. We have applied the 'private' change721 if private_changed:
714 # already, so updateBugFromData will only send an event if722 changed_fields.append('private')
715 # 'security_related' is changed, and we can't have that.723 if security_related_changed:
724 changed_fields.append('security_related')
716 notify(ObjectModifiedEvent(725 notify(ObjectModifiedEvent(
717 bug, bug_before_modification, ['private']))726 bug, bug_before_modification, changed_fields))
718727
719 # Apply other changes.728 # Apply other changes.
720 self.updateBugFromData(data)729 self.updateBugFromData(data)
721730
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py 2010-03-16 16:23:50 +0000
+++ lib/lp/bugs/browser/bugtarget.py 2010-04-16 04:01:13 +0000
@@ -36,7 +36,7 @@
36from zope.interface import implements36from zope.interface import implements
37from zope.publisher.interfaces import NotFound37from zope.publisher.interfaces import NotFound
38from zope.publisher.interfaces.browser import IBrowserPublisher38from zope.publisher.interfaces.browser import IBrowserPublisher
39from zope.schema import Choice39from zope.schema import Bool, Choice
40from zope.schema.vocabulary import SimpleVocabulary40from zope.schema.vocabulary import SimpleVocabulary
4141
42from canonical.cachedproperty import cachedproperty42from canonical.cachedproperty import cachedproperty
@@ -45,6 +45,7 @@
45from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource45from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource
46from lp.bugs.interfaces.bug import IBug46from lp.bugs.interfaces.bug import IBug
47from lp.bugs.interfaces.bugtask import BugTaskSearchParams47from lp.bugs.interfaces.bugtask import BugTaskSearchParams
48from canonical.launchpad import _
48from canonical.launchpad.browser.feeds import (49from canonical.launchpad.browser.feeds import (
49 BugFeedLink, BugTargetLatestBugsFeedLink, FeedsMixin)50 BugFeedLink, BugTargetLatestBugsFeedLink, FeedsMixin)
50from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor51from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
@@ -297,6 +298,14 @@
297 self.form_fields = self.form_fields.omit('subscribe_to_existing_bug')298 self.form_fields = self.form_fields.omit('subscribe_to_existing_bug')
298 self.form_fields += formlib.form.Fields(subscribe_field)299 self.form_fields += formlib.form.Fields(subscribe_field)
299300
301 security_related_field = Bool(
302 __name__='security_related',
303 title=_("This bug is a security vulnerability"),
304 required=False, default=False)
305
306 self.form_fields = self.form_fields.omit('security_related')
307 self.form_fields += formlib.form.Fields(security_related_field)
308
300 def contextUsesMalone(self):309 def contextUsesMalone(self):
301 """Does the context use Malone as its official bugtracker?"""310 """Does the context use Malone as its official bugtracker?"""
302 if IProjectGroup.providedBy(self.context):311 if IProjectGroup.providedBy(self.context):
303312
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2010-04-08 08:55:10 +0000
+++ lib/lp/bugs/configure.zcml 2010-04-16 04:01:13 +0000
@@ -681,6 +681,7 @@
681 expireNotifications681 expireNotifications
682 setStatus682 setStatus
683 setPrivate683 setPrivate
684 setSecurityRelated
684 convertToQuestion685 convertToQuestion
685 markUserAffected686 markUserAffected
686 addTask687 addTask
687688
=== modified file 'lib/lp/bugs/doc/bug-heat.txt'
--- lib/lp/bugs/doc/bug-heat.txt 2010-04-12 07:11:47 +0000
+++ lib/lp/bugs/doc/bug-heat.txt 2010-04-16 04:01:13 +0000
@@ -29,6 +29,28 @@
29 datetime.datetime(..., tzinfo=<UTC>)29 datetime.datetime(..., tzinfo=<UTC>)
3030
3131
32Adjusting bug heat in transaction
33---------------------------------
34
35Sometimes, when a bug changes, we want to see the changes reflected in the bug's
36heat value immidiately, without waiting for heat to be recalculated. Currently
37we adjust heat immidiately for bug privacy and security.
38
39 >>> bug_owner = factory.makePerson()
40 >>> bug = factory.makeBug(owner=bug_owner)
41 >>> bug.heat
42 0
43 >>> changed = bug.setPrivate(True, bug_owner)
44 >>> bug.heat
45 150
46 >>> changed = bug.setSecurityRelated(True)
47 >>> bug.heat
48 400
49 >>> changed = bug.setPrivate(False, bug_owner)
50 >>> bug.heat
51 250
52
53
32Getting bugs whose heat is outdated54Getting bugs whose heat is outdated
33-----------------------------------55-----------------------------------
3456
3557
=== modified file 'lib/lp/bugs/doc/bug.txt'
--- lib/lp/bugs/doc/bug.txt 2010-02-11 05:08:47 +0000
+++ lib/lp/bugs/doc/bug.txt 2010-04-16 04:01:13 +0000
@@ -747,7 +747,7 @@
747747
748 >>> firefox_bug.security_related748 >>> firefox_bug.security_related
749 False749 False
750 >>> firefox_bug.security_related = True750 >>> changed = firefox_bug.setSecurityRelated(True)
751751
752 >>> bug_security_changed = ObjectModifiedEvent(752 >>> bug_security_changed = ObjectModifiedEvent(
753 ... firefox_bug, bug_before_modification, ["security_related"])753 ... firefox_bug, bug_before_modification, ["security_related"])
754754
=== modified file 'lib/lp/bugs/doc/bugnotification-email.txt'
--- lib/lp/bugs/doc/bugnotification-email.txt 2010-01-20 17:09:40 +0000
+++ lib/lp/bugs/doc/bugnotification-email.txt 2010-04-16 04:01:13 +0000
@@ -85,7 +85,7 @@
8585
86New security related bugs are sent with a prominent warning:86New security related bugs are sent with a prominent warning:
8787
88 >>> bug_four.security_related = True88 >>> changed = bug_four.setSecurityRelated(True)
8989
90 >>> subject, body = generate_bug_add_email(bug_four)90 >>> subject, body = generate_bug_add_email(bug_four)
91 >>> subject91 >>> subject
@@ -202,7 +202,7 @@
202202
203 >>> edited_bug.setPrivate(True, getUtility(ILaunchBag).user)203 >>> edited_bug.setPrivate(True, getUtility(ILaunchBag).user)
204 True204 True
205 >>> edited_bug.security_related = True205 >>> changed = edited_bug.setSecurityRelated(True)
206 >>> bug_delta = BugDelta(206 >>> bug_delta = BugDelta(
207 ... bug=edited_bug,207 ... bug=edited_bug,
208 ... bugurl="http://www.example.com/bugs/2",208 ... bugurl="http://www.example.com/bugs/2",
@@ -225,7 +225,7 @@
225225
226 >>> edited_bug.setPrivate(False, getUtility(ILaunchBag).user)226 >>> edited_bug.setPrivate(False, getUtility(ILaunchBag).user)
227 True227 True
228 >>> edited_bug.security_related = False228 >>> changed = edited_bug.setSecurityRelated(False)
229 >>> bug_delta = BugDelta(229 >>> bug_delta = BugDelta(
230 ... bug=edited_bug,230 ... bug=edited_bug,
231 ... bugurl="http://www.example.com/bugs/2",231 ... bugurl="http://www.example.com/bugs/2",
232232
=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
--- lib/lp/bugs/doc/bugnotification-sending.txt 2010-04-01 03:14:47 +0000
+++ lib/lp/bugs/doc/bugnotification-sending.txt 2010-04-16 04:01:13 +0000
@@ -1199,7 +1199,8 @@
1199The presence of the security flag on a bug is, surprise, denoted by a1199The presence of the security flag on a bug is, surprise, denoted by a
1200simple "yes":1200simple "yes":
12011201
1202 >>> bug_three.security_related = True1202 >>> bug_three.setSecurityRelated(True)
1203 True
1203 >>> bug_three.security_related1204 >>> bug_three.security_related
1204 True1205 True
12051206
12061207
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2010-04-12 14:48:34 +0000
+++ lib/lp/bugs/interfaces/bug.py 2010-04-16 04:01:13 +0000
@@ -207,7 +207,7 @@
207 readonly=True))207 readonly=True))
208 security_related = exported(208 security_related = exported(
209 Bool(title=_("This bug is a security vulnerability"),209 Bool(title=_("This bug is a security vulnerability"),
210 required=False, default=False))210 required=False, default=False, readonly=True))
211 displayname = TextLine(title=_("Text of the form 'Bug #X"),211 displayname = TextLine(title=_("Text of the form 'Bug #X"),
212 readonly=True)212 readonly=True)
213 activity = Attribute('SQLObject.Multijoin of IBugActivity')213 activity = Attribute('SQLObject.Multijoin of IBugActivity')
@@ -705,6 +705,17 @@
705 Return True if a change is made, False otherwise.705 Return True if a change is made, False otherwise.
706 """706 """
707707
708 @mutator_for(security_related)
709 @operation_parameters(security_related=copy_field(security_related))
710 @export_write_operation()
711 def setSecurityRelated(security_related):
712 """Set bug security.
713
714 :security_related: True/False.
715
716 Return True if a change is made, False otherwise.
717 """
718
708 def getBugTask(target):719 def getBugTask(target):
709 """Return the bugtask with the specified target.720 """Return the bugtask with the specified target.
710721
711722
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2010-04-12 14:48:34 +0000
+++ lib/lp/bugs/model/bug.py 2010-04-16 04:01:13 +0000
@@ -83,6 +83,7 @@
83from lp.bugs.interfaces.bugtracker import BugTrackerType83from lp.bugs.interfaces.bugtracker import BugTrackerType
84from lp.bugs.interfaces.bugwatch import IBugWatchSet84from lp.bugs.interfaces.bugwatch import IBugWatchSet
85from lp.bugs.interfaces.cve import ICveSet85from lp.bugs.interfaces.cve import ICveSet
86from lp.bugs.scripts.bugheat import BugHeatConstants
86from lp.bugs.model.bugattachment import BugAttachment87from lp.bugs.model.bugattachment import BugAttachment
87from lp.bugs.model.bugbranch import BugBranch88from lp.bugs.model.bugbranch import BugBranch
88from lp.bugs.model.bugcve import BugCve89from lp.bugs.model.bugcve import BugCve
@@ -1351,10 +1352,33 @@
1351 self.who_made_private = None1352 self.who_made_private = None
1352 self.date_made_private = None1353 self.date_made_private = None
13531354
1355 # Correct the heat for the bug immediately, so that we don't have
1356 # to wait for the next calculation job for the adjusted heat.
1357 if private:
1358 self.setHeat(self.heat + BugHeatConstants.PRIVACY)
1359 else:
1360 self.setHeat(self.heat - BugHeatConstants.PRIVACY)
1361
1354 return True # Changed.1362 return True # Changed.
1355 else:1363 else:
1356 return False # Not changed.1364 return False # Not changed.
13571365
1366 def setSecurityRelated(self, security_related):
1367 """Setter for the `security_related` property."""
1368 if self.security_related != security_related:
1369 self.security_related = security_related
1370
1371 # Correct the heat for the bug immediately, so that we don't have
1372 # to wait for the next calculation job for the adjusted heat.
1373 if security_related:
1374 self.setHeat(self.heat + BugHeatConstants.SECURITY)
1375 else:
1376 self.setHeat(self.heat - BugHeatConstants.SECURITY)
1377
1378 return True # Changed
1379 else:
1380 return False # Unchanged
1381
1358 def getBugTask(self, target):1382 def getBugTask(self, target):
1359 """See `IBug`."""1383 """See `IBug`."""
1360 for bugtask in self.bugtasks:1384 for bugtask in self.bugtasks:
@@ -1540,6 +1564,9 @@
1540 if timestamp is None:1564 if timestamp is None:
1541 timestamp = UTC_NOW1565 timestamp = UTC_NOW
15421566
1567 if heat < 0:
1568 heat = 0
1569
1543 self.heat = heat1570 self.heat = heat
1544 self.heat_last_updated = timestamp1571 self.heat_last_updated = timestamp
1545 for task in self.bugtasks:1572 for task in self.bugtasks:
15461573
=== modified file 'lib/lp/bugs/scripts/bugheat.py'
--- lib/lp/bugs/scripts/bugheat.py 2010-03-04 19:49:08 +0000
+++ lib/lp/bugs/scripts/bugheat.py 2010-04-16 04:01:13 +0000
@@ -6,6 +6,7 @@
6__metaclass__ = type6__metaclass__ = type
7__all__ = [7__all__ = [
8 'BugHeatCalculator',8 'BugHeatCalculator',
9 'BugHeatConstants',
9 ]10 ]
1011
11from datetime import datetime12from datetime import datetime
1213
=== modified file 'lib/lp/bugs/scripts/bugimport.py'
--- lib/lp/bugs/scripts/bugimport.py 2009-09-11 14:59:08 +0000
+++ lib/lp/bugs/scripts/bugimport.py 2010-04-16 04:01:13 +0000
@@ -328,7 +328,7 @@
328328
329 # set up bug329 # set up bug
330 bug.setPrivate(get_value(bugnode, 'private') == 'True', owner)330 bug.setPrivate(get_value(bugnode, 'private') == 'True', owner)
331 bug.security_related = (331 bug.setSecurityRelated(
332 get_value(bugnode, 'security_related') == 'True')332 get_value(bugnode, 'security_related') == 'True')
333 bug.name = get_value(bugnode, 'nickname')333 bug.name = get_value(bugnode, 'nickname')
334 description = get_value(bugnode, 'description')334 description = get_value(bugnode, 'description')
335335
=== modified file 'lib/lp/bugs/scripts/tests/test_bugheat.py'
--- lib/lp/bugs/scripts/tests/test_bugheat.py 2010-03-03 16:05:57 +0000
+++ lib/lp/bugs/scripts/tests/test_bugheat.py 2010-04-16 04:01:13 +0000
@@ -155,7 +155,7 @@
155155
156 # If, on the other hand, the bug is security_related,156 # If, on the other hand, the bug is security_related,
157 # _getHeatFromSecurity() will return BugHeatConstants.SECURITY157 # _getHeatFromSecurity() will return BugHeatConstants.SECURITY
158 self.bug.security_related = True158 self.bug.setSecurityRelated(True)
159 self.assertEqual(159 self.assertEqual(
160 BugHeatConstants.SECURITY, self.calculator._getHeatFromSecurity())160 BugHeatConstants.SECURITY, self.calculator._getHeatFromSecurity())
161161
@@ -179,7 +179,7 @@
179 dupe = self.factory.makeBug()179 dupe = self.factory.makeBug()
180 dupe.duplicateof = self.bug180 dupe.duplicateof = self.bug
181 self.bug.setPrivate(True, self.bug.owner)181 self.bug.setPrivate(True, self.bug.owner)
182 self.bug.security_related = True182 self.bug.setSecurityRelated(True)
183183
184 expected_heat += (184 expected_heat += (
185 BugHeatConstants.DUPLICATE +185 BugHeatConstants.DUPLICATE +
186186
=== modified file 'lib/lp/bugs/tests/test_bugchanges.py'
--- lib/lp/bugs/tests/test_bugchanges.py 2009-12-05 18:37:28 +0000
+++ lib/lp/bugs/tests/test_bugchanges.py 2010-04-16 04:01:13 +0000
@@ -562,7 +562,7 @@
562 def test_mark_as_security_vulnerability(self):562 def test_mark_as_security_vulnerability(self):
563 # Marking a bug as a security vulnerability adds to the bug's563 # Marking a bug as a security vulnerability adds to the bug's
564 # activity log and sends a notification.564 # activity log and sends a notification.
565 self.bug.security_related = False565 self.bug.setSecurityRelated(False)
566 self.changeAttribute(self.bug, 'security_related', True)566 self.changeAttribute(self.bug, 'security_related', True)
567567
568 security_change_activity = {568 security_change_activity = {
@@ -586,7 +586,7 @@
586 def test_unmark_as_security_vulnerability(self):586 def test_unmark_as_security_vulnerability(self):
587 # Unmarking a bug as a security vulnerability adds to the587 # Unmarking a bug as a security vulnerability adds to the
588 # bug's activity log and sends a notification.588 # bug's activity log and sends a notification.
589 self.bug.security_related = True589 self.bug.setSecurityRelated(True)
590 self.changeAttribute(self.bug, 'security_related', False)590 self.changeAttribute(self.bug, 'security_related', False)
591591
592 security_change_activity = {592 security_change_activity = {
593593
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2010-03-25 15:28:49 +0000
+++ lib/lp/code/browser/branch.py 2010-04-16 04:01:13 +0000
@@ -81,10 +81,12 @@
81from lp.code.browser.branchmergeproposal import (81from lp.code.browser.branchmergeproposal import (
82 latest_proposals_for_each_branch)82 latest_proposals_for_each_branch)
83from lp.code.enums import (83from lp.code.enums import (
84 BranchLifecycleStatus, BranchType, CodeImportJobState,84 BranchLifecycleStatus, BranchType,
85 CodeImportResultStatus, CodeImportReviewStatus, RevisionControlSystems,85 CodeImportResultStatus, CodeImportReviewStatus, RevisionControlSystems,
86 UICreatableBranchType)86 UICreatableBranchType)
87from lp.code.errors import InvalidBranchMergeProposal87from lp.code.errors import (
88 CodeImportAlreadyRequested, CodeImportAlreadyRunning,
89 CodeImportNotInReviewedState, InvalidBranchMergeProposal)
88from lp.code.interfaces.branch import (90from lp.code.interfaces.branch import (
89 BranchCreationForbidden, BranchExists, IBranch,91 BranchCreationForbidden, BranchExists, IBranch,
90 user_has_special_branch_access)92 user_has_special_branch_access)
@@ -1317,26 +1319,23 @@
13171319
1318 @action('Import Now', name='request')1320 @action('Import Now', name='request')
1319 def request_import_action(self, action, data):1321 def request_import_action(self, action, data):
1320 if self.context.code_import.import_job is None:1322 try:
1323 self.context.code_import.requestImport(
1324 self.user, error_if_already_requested=True)
1325 self.request.response.addNotification(
1326 "Import will run as soon as possible.")
1327 except CodeImportNotInReviewedState:
1321 self.request.response.addNotification(1328 self.request.response.addNotification(
1322 "This import is no longer being updated automatically.")1329 "This import is no longer being updated automatically.")
1323 elif (self.context.code_import.import_job.state !=1330 except CodeImportAlreadyRunning:
1324 CodeImportJobState.PENDING):
1325 assert (self.context.code_import.import_job.state ==
1326 CodeImportJobState.RUNNING)
1327 self.request.response.addNotification(1331 self.request.response.addNotification(
1328 "The import is already running.")1332 "The import is already running.")
1329 elif self.context.code_import.import_job.requesting_user is not None:1333 except CodeImportAlreadyRequested, e:
1330 user = self.context.code_import.import_job.requesting_user1334 user = e.requesting_user
1331 adapter = queryAdapter(user, IPathAdapter, 'fmt')1335 adapter = queryAdapter(user, IPathAdapter, 'fmt')
1332 self.request.response.addNotification(1336 self.request.response.addNotification(
1333 structured("The import has already been requested by %s." %1337 structured("The import has already been requested by %s." %
1334 adapter.link(None)))1338 adapter.link(None)))
1335 else:
1336 getUtility(ICodeImportJobWorkflow).requestJob(
1337 self.context.code_import.import_job, self.user)
1338 self.request.response.addNotification(
1339 "Import will run as soon as possible.")
13401339
1341 @property1340 @property
1342 def prefix(self):1341 def prefix(self):
13431342
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2010-04-13 23:46:06 +0000
+++ lib/lp/code/configure.zcml 2010-04-16 04:01:13 +0000
@@ -840,7 +840,8 @@
840 getImportDetailsForDisplay"/>840 getImportDetailsForDisplay"/>
841 <require841 <require
842 permission="launchpad.AnyPerson"842 permission="launchpad.AnyPerson"
843 attributes="tryFailingImportAgain"/>843 attributes="tryFailingImportAgain
844 requestImport"/>
844 <require845 <require
845 permission="launchpad.Edit"846 permission="launchpad.Edit"
846 attributes="updateFromData"/>847 attributes="updateFromData"/>
847848
=== modified file 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py 2010-04-05 21:38:40 +0000
+++ lib/lp/code/errors.py 2010-04-16 04:01:13 +0000
@@ -8,6 +8,9 @@
8 'BadBranchMergeProposalSearchContext',8 'BadBranchMergeProposalSearchContext',
9 'BadStateTransition',9 'BadStateTransition',
10 'BranchMergeProposalExists',10 'BranchMergeProposalExists',
11 'CodeImportAlreadyRequested',
12 'CodeImportAlreadyRunning',
13 'CodeImportNotInReviewedState',
11 'ClaimReviewFailed',14 'ClaimReviewFailed',
12 'InvalidBranchMergeProposal',15 'InvalidBranchMergeProposal',
13 'ReviewNotPending',16 'ReviewNotPending',
@@ -68,3 +71,23 @@
6871
69class UnknownBranchTypeError(Exception):72class UnknownBranchTypeError(Exception):
70 """Raised when the user specifies an unrecognized branch type."""73 """Raised when the user specifies an unrecognized branch type."""
74
75
76class CodeImportNotInReviewedState(Exception):
77 """Raised when the user requests an import of a non-automatic import."""
78
79 webservice_error(400)
80
81
82class CodeImportAlreadyRequested(Exception):
83 """Raised when the user requests an import that is already requested."""
84
85 def __init__(self, msg, requesting_user):
86 super(CodeImportAlreadyRequested, self).__init__(msg)
87 self.requesting_user = requesting_user
88
89
90class CodeImportAlreadyRunning(Exception):
91 """Raised when the user requests an import that is already running."""
92
93 webservice_error(400)
7194
=== modified file 'lib/lp/code/interfaces/branchmergeproposal.py'
--- lib/lp/code/interfaces/branchmergeproposal.py 2010-04-01 05:08:47 +0000
+++ lib/lp/code/interfaces/branchmergeproposal.py 2010-04-16 04:01:13 +0000
@@ -347,6 +347,9 @@
347 If the proposal is not in the Approved state before this method347 If the proposal is not in the Approved state before this method
348 is called, approveBranch is called with the reviewer and revision_id348 is called, approveBranch is called with the reviewer and revision_id
349 specified.349 specified.
350
351 If None is supplied as the revision_id, the proposals
352 reviewed_revision_id is used.
350 """353 """
351354
352 def dequeue():355 def dequeue():
353356
=== modified file 'lib/lp/code/interfaces/codeimport.py'
--- lib/lp/code/interfaces/codeimport.py 2010-04-01 20:58:42 +0000
+++ lib/lp/code/interfaces/codeimport.py 2010-04-16 04:01:13 +0000
@@ -25,7 +25,8 @@
25from lp.code.interfaces.branch import IBranch25from lp.code.interfaces.branch import IBranch
2626
27from lazr.restful.declarations import (27from lazr.restful.declarations import (
28 export_as_webservice_entry, exported)28 call_with, export_as_webservice_entry, exported, export_write_operation,
29 REQUEST_USER)
29from lazr.restful.fields import ReferenceChoice30from lazr.restful.fields import ReferenceChoice
3031
3132
@@ -177,6 +178,33 @@
177 :param user: the user who is requesting the import be tried again.178 :param user: the user who is requesting the import be tried again.
178 """179 """
179180
181 @call_with(requester=REQUEST_USER)
182 @export_write_operation()
183 def requestImport(requester, error_if_already_requested=False):
184 """Request that an import be tried soon.
185
186 This method will schedule an import to happen soon for this branch.
187
188 The import must be in the Reviewed state, if not then a
189 CodeImportNotInReviewedState error will be thrown. If using the
190 API then a status code of 400 will result.
191
192 If the import is already running then a CodeImportAlreadyRunning
193 error will be thrown. If using the API then a status code of
194 400 will result.
195
196 The two cases can be distinguished over the API by seeing if the
197 exception names appear in the body of the response.
198
199 If used over the API and the request has already been made then this
200 method will silently do nothing.
201 If called internally then the error_if_already_requested parameter
202 controls whether a CodeImportAlreadyRequested exception will be
203 thrown in that situation.
204
205 :return: None
206 """
207
180208
181class ICodeImportSet(Interface):209class ICodeImportSet(Interface):
182 """Interface representing the set of code imports."""210 """Interface representing the set of code imports."""
183211
=== modified file 'lib/lp/code/interfaces/webservice.py'
--- lib/lp/code/interfaces/webservice.py 2010-04-01 23:04:10 +0000
+++ lib/lp/code/interfaces/webservice.py 2010-04-16 04:01:13 +0000
@@ -3,12 +3,23 @@
33
4"""All the interfaces that are exposed through the webservice."""4"""All the interfaces that are exposed through the webservice."""
55
6<<<<<<< TREE
6# The exceptions are imported so that they can produce the special7# The exceptions are imported so that they can produce the special
7# status code defined by webservice_error when they are raised.8# status code defined by webservice_error when they are raised.
8from lp.code.errors import BranchMergeProposalExists9from lp.code.errors import BranchMergeProposalExists
9from lp.code.interfaces.branch import (10from lp.code.interfaces.branch import (
10 IBranch, IBranchSet, BranchCreatorNotMemberOfOwnerTeam,11 IBranch, IBranchSet, BranchCreatorNotMemberOfOwnerTeam,
11 BranchCreatorNotOwner, BranchExists)12 BranchCreatorNotOwner, BranchExists)
13=======
14# The exceptions are imported so that they can produce the special
15# status code defined by webservice_error when they are raised.
16from lp.code.errors import (
17 BranchMergeProposalExists, CodeImportAlreadyRunning,
18 CodeImportNotInReviewedState)
19from lp.code.interfaces.branch import (
20 IBranch, IBranchSet, BranchCreatorNotMemberOfOwnerTeam,
21 BranchCreatorNotOwner, BranchExists)
22>>>>>>> MERGE-SOURCE
12from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal23from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
13from lp.code.interfaces.branchsubscription import IBranchSubscription24from lp.code.interfaces.branchsubscription import IBranchSubscription
14from lp.code.interfaces.codeimport import ICodeImport25from lp.code.interfaces.codeimport import ICodeImport
1526
=== modified file 'lib/lp/code/model/branchmergeproposal.py'
--- lib/lp/code/model/branchmergeproposal.py 2010-04-01 04:48:01 +0000
+++ lib/lp/code/model/branchmergeproposal.py 2010-04-16 04:01:13 +0000
@@ -84,9 +84,10 @@
84 if (next_state == rejected and not valid_reviewer):84 if (next_state == rejected and not valid_reviewer):
85 return False85 return False
86 # Non-reviewers can toggle between code_approved and queued, but not86 # Non-reviewers can toggle between code_approved and queued, but not
87 # make anything else approved or queued.87 # make anything else approved or queued. They can also take merge failed
88 # and requeue or bounce all the way out to approved again.
88 elif (next_state in (code_approved, queued) and89 elif (next_state in (code_approved, queued) and
89 from_state not in (code_approved, queued)90 from_state not in (code_approved, queued, merge_failed)
90 and not valid_reviewer):91 and not valid_reviewer):
91 return False92 return False
92 else:93 else:
@@ -325,23 +326,23 @@
325 # XXX - rockstar - 9 Oct 2008 - jml suggested in a review that this326 # XXX - rockstar - 9 Oct 2008 - jml suggested in a review that this
326 # would be better as a dict mapping.327 # would be better as a dict mapping.
327 # See bug #281060.328 # See bug #281060.
329 if (self.queue_status == BranchMergeProposalStatus.QUEUED and
330 status != BranchMergeProposalStatus.QUEUED):
331 self.dequeue()
328 if status == BranchMergeProposalStatus.WORK_IN_PROGRESS:332 if status == BranchMergeProposalStatus.WORK_IN_PROGRESS:
329 self.setAsWorkInProgress()333 self.setAsWorkInProgress()
330 elif status == BranchMergeProposalStatus.NEEDS_REVIEW:334 elif status == BranchMergeProposalStatus.NEEDS_REVIEW:
331 self.requestReview()335 self.requestReview()
332 elif status == BranchMergeProposalStatus.CODE_APPROVED:336 elif status == BranchMergeProposalStatus.CODE_APPROVED:
333 # Other half of the edge case. If the status is currently queued,337 self.approveBranch(user, revision_id)
334 # we need to dequeue, otherwise we just approve the branch.
335 if self.queue_status == BranchMergeProposalStatus.QUEUED:
336 self.dequeue()
337 else:
338 self.approveBranch(user, revision_id)
339 elif status == BranchMergeProposalStatus.REJECTED:338 elif status == BranchMergeProposalStatus.REJECTED:
340 self.rejectBranch(user, revision_id)339 self.rejectBranch(user, revision_id)
341 elif status == BranchMergeProposalStatus.QUEUED:340 elif status == BranchMergeProposalStatus.QUEUED:
342 self.enqueue(user, revision_id)341 self.enqueue(user, revision_id)
343 elif status == BranchMergeProposalStatus.MERGED:342 elif status == BranchMergeProposalStatus.MERGED:
344 self.markAsMerged(merge_reporter=user)343 self.markAsMerged(merge_reporter=user)
344 elif status == BranchMergeProposalStatus.MERGE_FAILED:
345 self._transitionToState(status)
345 else:346 else:
346 raise AssertionError('Unexpected queue status: ' % status)347 raise AssertionError('Unexpected queue status: ' % status)
347348
@@ -379,7 +380,7 @@
379380
380 def _reviewProposal(self, reviewer, next_state, revision_id,381 def _reviewProposal(self, reviewer, next_state, revision_id,
381 _date_reviewed=None):382 _date_reviewed=None):
382 """Set the proposal to one of the two review statuses."""383 """Set the proposal to next_state."""
383 # Check the reviewer can review the code for the target branch.384 # Check the reviewer can review the code for the target branch.
384 old_state = self.queue_status385 old_state = self.queue_status
385 if not self.target_branch.isPersonTrustedReviewer(reviewer):386 if not self.target_branch.isPersonTrustedReviewer(reviewer):
@@ -433,7 +434,7 @@
433 self.queue_status = BranchMergeProposalStatus.QUEUED434 self.queue_status = BranchMergeProposalStatus.QUEUED
434 self.queue_position = position435 self.queue_position = position
435 self.queuer = queuer436 self.queuer = queuer
436 self.queued_revision_id = revision_id437 self.queued_revision_id = revision_id or self.reviewed_revision_id
437 self.date_queued = UTC_NOW438 self.date_queued = UTC_NOW
438 self.syncUpdate()439 self.syncUpdate()
439440
440441
=== modified file 'lib/lp/code/model/branchtarget.py'
--- lib/lp/code/model/branchtarget.py 2010-04-14 17:44:00 +0000
+++ lib/lp/code/model/branchtarget.py 2010-04-16 04:01:13 +0000
@@ -337,6 +337,26 @@
337 branch.sourcepackagename = None337 branch.sourcepackagename = None
338338
339339
340<<<<<<< TREE
341=======
342class ProductSeriesBranchTarget(ProductBranchTarget):
343
344 def __init__(self, productseries):
345 ProductBranchTarget.__init__(self, productseries.product)
346 self.productseries = productseries
347
348 @property
349 def context(self):
350 """See `IBranchTarget`."""
351 return self.productseries
352
353 @property
354 def supports_code_imports(self):
355 """See `IBranchTarget`."""
356 return False
357
358
359>>>>>>> MERGE-SOURCE
340def get_canonical_url_data_for_target(branch_target):360def get_canonical_url_data_for_target(branch_target):
341 """Return the `ICanonicalUrlData` for an `IBranchTarget`."""361 """Return the `ICanonicalUrlData` for an `IBranchTarget`."""
342 return ICanonicalUrlData(branch_target.context)362 return ICanonicalUrlData(branch_target.context)
343363
=== modified file 'lib/lp/code/model/codeimport.py'
--- lib/lp/code/model/codeimport.py 2010-04-01 20:58:42 +0000
+++ lib/lp/code/model/codeimport.py 2010-04-16 04:01:13 +0000
@@ -37,6 +37,9 @@
37from lp.code.enums import (37from lp.code.enums import (
38 BranchType, CodeImportJobState, CodeImportResultStatus,38 BranchType, CodeImportJobState, CodeImportResultStatus,
39 CodeImportReviewStatus, RevisionControlSystems)39 CodeImportReviewStatus, RevisionControlSystems)
40from lp.code.errors import (
41 CodeImportAlreadyRequested, CodeImportAlreadyRunning,
42 CodeImportNotInReviewedState)
40from lp.code.interfaces.codeimport import ICodeImport, ICodeImportSet43from lp.code.interfaces.codeimport import ICodeImport, ICodeImportSet
41from lp.code.interfaces.codeimportevent import ICodeImportEventSet44from lp.code.interfaces.codeimportevent import ICodeImportEventSet
42from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow45from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow
@@ -196,6 +199,27 @@
196 {'review_status': CodeImportReviewStatus.REVIEWED}, user)199 {'review_status': CodeImportReviewStatus.REVIEWED}, user)
197 getUtility(ICodeImportJobWorkflow).requestJob(self.import_job, user)200 getUtility(ICodeImportJobWorkflow).requestJob(self.import_job, user)
198201
202 def requestImport(self, requester, error_if_already_requested=False):
203 """See `ICodeImport`."""
204 if self.import_job is None: # not in automatic mode
205 raise CodeImportNotInReviewedState("This code import is %s, and "
206 "must be Reviewed for you to call requestImport."
207 % self.review_status.name)
208 if (self.import_job.state != CodeImportJobState.PENDING):
209 assert (self.import_job.state == CodeImportJobState.RUNNING)
210 # Already running
211 raise CodeImportAlreadyRunning("This code import is already "
212 "running.")
213 elif self.import_job.requesting_user is not None:
214 if error_if_already_requested:
215 raise CodeImportAlreadyRequested("This code import has "
216 "already been requested to run.",
217 self.import_job.requesting_user)
218 else:
219 getUtility(ICodeImportJobWorkflow).requestJob(
220 self.import_job, requester)
221 return None
222
199223
200class CodeImportSet:224class CodeImportSet:
201 """See `ICodeImportSet`."""225 """See `ICodeImportSet`."""
202226
=== modified file 'lib/lp/code/model/tests/test_branchmergeproposals.py'
--- lib/lp/code/model/tests/test_branchmergeproposals.py 2010-04-06 03:37:16 +0000
+++ lib/lp/code/model/tests/test_branchmergeproposals.py 2010-04-16 04:01:13 +0000
@@ -261,6 +261,20 @@
261 """We can go from merge failed to any other state."""261 """We can go from merge failed to any other state."""
262 self.assertAllTransitionsGood(BranchMergeProposalStatus.MERGE_FAILED)262 self.assertAllTransitionsGood(BranchMergeProposalStatus.MERGE_FAILED)
263263
264 def test_transition_from_merge_failed_to_queued_non_reviewer(self):
265 # Contributors can requeue to retry after environmental issues fail a
266 # merge.
267 proposal = self.factory.makeBranchMergeProposal()
268 self.assertFalse(proposal.target_branch.isPersonTrustedReviewer(
269 proposal.source_branch.owner))
270 # It is always valid to go to the same state.
271 self.assertValidTransitions(set([
272 BranchMergeProposalStatus.MERGE_FAILED,
273 BranchMergeProposalStatus.CODE_APPROVED,
274 BranchMergeProposalStatus.QUEUED]),
275 proposal, BranchMergeProposalStatus.QUEUED,
276 proposal.source_branch.owner)
277
264 def test_transitions_from_queued_dequeue(self):278 def test_transitions_from_queued_dequeue(self):
265 # When a proposal is dequeued it is set to code approved, and the279 # When a proposal is dequeued it is set to code approved, and the
266 # queue position is reset.280 # queue position is reset.
@@ -322,6 +336,19 @@
322 self.target_branch = self.factory.makeProductBranch()336 self.target_branch = self.factory.makeProductBranch()
323 login_person(self.target_branch.owner)337 login_person(self.target_branch.owner)
324338
339 def test_set_status_approved_to_queued(self):
340 # setState can change an approved merge proposal to Work In Progress,
341 # which will set the revision id to the reviewed revision id if not
342 # supplied.
343 proposal = self.factory.makeBranchMergeProposal(
344 target_branch=self.target_branch,
345 set_state=BranchMergeProposalStatus.CODE_APPROVED)
346 proposal.approveBranch(proposal.target_branch.owner, '250')
347 proposal.setStatus(BranchMergeProposalStatus.QUEUED)
348 self.assertEqual(proposal.queue_status,
349 BranchMergeProposalStatus.QUEUED)
350 self.assertEqual(proposal.queued_revision_id, '250')
351
325 def test_set_status_approved_to_work_in_progress(self):352 def test_set_status_approved_to_work_in_progress(self):
326 # setState can change an approved merge proposal to Work In Progress.353 # setState can change an approved merge proposal to Work In Progress.
327 proposal = self.factory.makeBranchMergeProposal(354 proposal = self.factory.makeBranchMergeProposal(
@@ -331,6 +358,18 @@
331 self.assertEqual(proposal.queue_status,358 self.assertEqual(proposal.queue_status,
332 BranchMergeProposalStatus.WORK_IN_PROGRESS)359 BranchMergeProposalStatus.WORK_IN_PROGRESS)
333360
361 def test_set_status_queued_to_merge_failed(self):
362 proposal = self.factory.makeBranchMergeProposal(
363 target_branch=self.target_branch,
364 set_state=BranchMergeProposalStatus.QUEUED)
365 proposal.setStatus(BranchMergeProposalStatus.MERGE_FAILED)
366 self.assertEqual(proposal.queue_status,
367 BranchMergeProposalStatus.MERGE_FAILED)
368 self.assertEqual(proposal.queuer, None)
369 self.assertEqual(proposal.queued_revision_id, None)
370 self.assertEqual(proposal.date_queued, None)
371 self.assertEqual(proposal.queue_position, None)
372
334 def test_set_status_wip_to_needs_review(self):373 def test_set_status_wip_to_needs_review(self):
335 # setState can change the merge proposal to Needs Review.374 # setState can change the merge proposal to Needs Review.
336 proposal = self.factory.makeBranchMergeProposal(375 proposal = self.factory.makeBranchMergeProposal(
337376
=== modified file 'lib/lp/code/model/tests/test_codeimport.py'
--- lib/lp/code/model/tests/test_codeimport.py 2010-04-01 20:58:42 +0000
+++ lib/lp/code/model/tests/test_codeimport.py 2010-04-16 04:01:13 +0000
@@ -12,6 +12,11 @@
12from zope.component import getUtility12from zope.component import getUtility
13from zope.security.proxy import removeSecurityProxy13from zope.security.proxy import removeSecurityProxy
1414
15from canonical.launchpad.testing.codeimporthelpers import (
16 make_running_import)
17from lp.code.errors import (
18 CodeImportAlreadyRequested, CodeImportAlreadyRunning,
19 CodeImportNotInReviewedState)
15from lp.code.model.codeimport import CodeImportSet20from lp.code.model.codeimport import CodeImportSet
16from lp.code.model.codeimportevent import CodeImportEvent21from lp.code.model.codeimportevent import CodeImportEvent
17from lp.code.model.codeimportjob import CodeImportJob, CodeImportJobSet22from lp.code.model.codeimportjob import CodeImportJob, CodeImportJobSet
@@ -620,5 +625,67 @@
620 requester, code_import.import_job.requesting_user)625 requester, code_import.import_job.requesting_user)
621626
622627
628class TestRequestImport(TestCaseWithFactory):
629 """Tests for `ICodeImport.requestImport`."""
630
631 layer = DatabaseFunctionalLayer
632
633 def setUp(self):
634 # We have to be logged in to request imports
635 TestCaseWithFactory.setUp(self, user='no-priv@canonical.com')
636
637 def test_requestsJob(self):
638 code_import = self.factory.makeCodeImport(
639 git_repo_url=self.factory.getUniqueURL())
640 requester = self.factory.makePerson()
641 old_date = code_import.import_job.date_due
642 code_import.requestImport(requester)
643 self.assertEqual(requester, code_import.import_job.requesting_user)
644 self.assertTrue(code_import.import_job.date_due <= old_date)
645
646 def test_noop_if_already_requested(self):
647 code_import = self.factory.makeCodeImport(
648 git_repo_url=self.factory.getUniqueURL())
649 requester = self.factory.makePerson()
650 code_import.requestImport(requester)
651 old_date = code_import.import_job.date_due
652 code_import.requestImport(requester)
653 # The checks don't matter so much, it's more that we don't get
654 # an exception.
655 self.assertEqual(requester, code_import.import_job.requesting_user)
656 self.assertEqual(old_date, code_import.import_job.date_due)
657
658 def test_optional_error_if_already_requested(self):
659 code_import = self.factory.makeCodeImport(
660 git_repo_url=self.factory.getUniqueURL())
661 requester = self.factory.makePerson()
662 code_import.requestImport(requester)
663 old_date = code_import.import_job.date_due
664 e = self.assertRaises(
665 CodeImportAlreadyRequested, code_import.requestImport, requester,
666 error_if_already_requested=True)
667 self.assertEqual(requester, e.requesting_user)
668
669 def test_exception_on_disabled(self):
670 # get an SVN request, which isn't reviewed by default
671 code_import = self.factory.makeCodeImport(
672 svn_branch_url=self.factory.getUniqueURL())
673 requester = self.factory.makePerson()
674 # which leads to an exception if we try and ask for an import
675 self.assertRaises(
676 CodeImportNotInReviewedState, code_import.requestImport,
677 requester)
678
679 def test_exception_if_already_running(self):
680 code_import = self.factory.makeCodeImport(
681 git_repo_url=self.factory.getUniqueURL())
682 code_import = make_running_import(factory=self.factory,
683 code_import=code_import)
684 requester = self.factory.makePerson()
685 self.assertRaises(
686 CodeImportAlreadyRunning, code_import.requestImport,
687 requester)
688
689
623def test_suite():690def test_suite():
624 return unittest.TestLoader().loadTestsFromName(__name__)691 return unittest.TestLoader().loadTestsFromName(__name__)
625692
=== modified file 'lib/lp/code/stories/webservice/xx-code-import.txt'
--- lib/lp/code/stories/webservice/xx-code-import.txt 2010-04-14 17:44:00 +0000
+++ lib/lp/code/stories/webservice/xx-code-import.txt 2010-04-16 04:01:13 +0000
@@ -17,9 +17,15 @@
17 >>> other_person = factory.makePerson(name='other-person')17 >>> other_person = factory.makePerson(name='other-person')
18 >>> removeSecurityProxy(person).join(team)18 >>> removeSecurityProxy(person).join(team)
19 >>> product = factory.makeProduct(name='scruff')19 >>> product = factory.makeProduct(name='scruff')
20 >>> svn_branch_url = "http://svn.domain.com/source"
20 >>> code_import = removeSecurityProxy(factory.makeProductCodeImport(21 >>> code_import = removeSecurityProxy(factory.makeProductCodeImport(
22<<<<<<< TREE
21 ... registrant=person, product=product, branch_name='import',23 ... registrant=person, product=product, branch_name='import',
22 ... svn_branch_url="http://svn.domain.com/source"))24 ... svn_branch_url="http://svn.domain.com/source"))
25=======
26 ... registrant=person, product=product, branch_name='import',
27 ... svn_branch_url=svn_branch_url))
28>>>>>>> MERGE-SOURCE
23 >>> no_import_branch = removeSecurityProxy(factory.makeProductBranch(29 >>> no_import_branch = removeSecurityProxy(factory.makeProductBranch(
24 ... owner=person, product=product, name='no-import'))30 ... owner=person, product=product, name='no-import'))
25 >>> logout()31 >>> logout()
@@ -58,171 +64,365 @@
58 >>> print representation['rcs_type']64 >>> print representation['rcs_type']
59 Subversion via CSCVS65 Subversion via CSCVS
60 >>> print representation['url']66 >>> print representation['url']
61 http://svn.domain.com/source67<<<<<<< TREE
62 >>> print representation['cvs_root']68 http://svn.domain.com/source
63 None69 >>> print representation['cvs_root']
64 >>> print representation['cvs_module']70 None
65 None71 >>> print representation['cvs_module']
66 >>> print representation['date_last_successful']72 None
67 None73 >>> print representation['date_last_successful']
6874 None
6975
70Package Branches76
71----------------77Package Branches
7278----------------
73The same is true for package branches.79
7480The same is true for package branches.
75 >>> login(ANONYMOUS)81
76 >>> distribution = factory.makeDistribution(name='scruffbuntu')82 >>> login(ANONYMOUS)
77 >>> distroseries = factory.makeDistroSeries(83 >>> distribution = factory.makeDistribution(name='scruffbuntu')
78 ... name='manic', distribution=distribution)84 >>> distroseries = factory.makeDistroSeries(
79 >>> source_package = factory.makeSourcePackage(85 ... name='manic', distribution=distribution)
80 ... sourcepackagename='scruff', distroseries=distroseries)86 >>> source_package = factory.makeSourcePackage(
81 >>> code_import = removeSecurityProxy(factory.makePackageCodeImport(87 ... sourcepackagename='scruff', distroseries=distroseries)
82 ... registrant=person, sourcepackage=source_package,88 >>> code_import = removeSecurityProxy(factory.makePackageCodeImport(
83 ... branch_name='import',89 ... registrant=person, sourcepackage=source_package,
84 ... svn_branch_url="http://svn.domain.com/package_source"))90 ... branch_name='import',
85 >>> logout()91 ... svn_branch_url="http://svn.domain.com/package_source"))
8692 >>> logout()
87There is a link on the branch object93
8894There is a link on the branch object
89 >>> branch_url = '/' + code_import.branch.unique_name95
90 >>> response = import_webservice.get(branch_url)96 >>> branch_url = '/' + code_import.branch.unique_name
91 >>> representation = response.jsonBody()97 >>> response = import_webservice.get(branch_url)
92 >>> print representation['code_import_link']98 >>> representation = response.jsonBody()
93 http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import99 >>> print representation['code_import_link']
94100 http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import
95and there is information available about the import itsef.101
96102and there is information available about the import itsef.
97 >>> import_url = representation['code_import_link']103
98 >>> response = import_webservice.get(import_url)104 >>> import_url = representation['code_import_link']
99 >>> representation = response.jsonBody()105 >>> response = import_webservice.get(import_url)
100 >>> print representation['self_link'] == import_url106 >>> representation = response.jsonBody()
101 True107 >>> print representation['self_link'] == import_url
102 >>> print representation['branch_link']108 True
103 http://.../~import-owner/scruffbuntu/manic/scruff/import109 >>> print representation['branch_link']
104 >>> print representation['review_status']110 http://.../~import-owner/scruffbuntu/manic/scruff/import
105 Pending Review111 >>> print representation['review_status']
106 >>> print representation['rcs_type']112 Pending Review
107 Subversion via CSCVS113 >>> print representation['rcs_type']
108 >>> print representation['url']114 Subversion via CSCVS
109 http://svn.domain.com/package_source115 >>> print representation['url']
110 >>> print representation['cvs_root']116 http://svn.domain.com/package_source
111 None117 >>> print representation['cvs_root']
112 >>> print representation['cvs_module']118 None
113 None119 >>> print representation['cvs_module']
114 >>> print representation['date_last_successful']120 None
115 None121 >>> print representation['date_last_successful']
116122 None
117123
118== Creating Imports ==124
119125== Creating Imports ==
120We can create an import using the API by calling a method on the project.126
121127We can create an import using the API by calling a method on the project.
122 >>> product_url = '/' + product.name128
123 >>> new_remote_url = factory.getUniqueURL()129 >>> product_url = '/' + product.name
124 >>> response = import_webservice.named_post(product_url, 'newCodeImport',130 >>> new_remote_url = factory.getUniqueURL()
125 ... branch_name='new-import', rcs_type='Git',131 >>> response = import_webservice.named_post(product_url, 'newCodeImport',
126 ... url=new_remote_url)132 ... branch_name='new-import', rcs_type='Git',
127 >>> print response.status133 ... url=new_remote_url)
128 201134 >>> print response.status
129 >>> location = response.getHeader('Location')135 201
130 >>> response = import_webservice.get(location)136 >>> location = response.getHeader('Location')
131 >>> representation = response.jsonBody()137 >>> response = import_webservice.get(location)
132 >>> print representation['self_link']138 >>> representation = response.jsonBody()
133 http://.../~import-owner/scruff/new-import/+code-import139 >>> print representation['self_link']
134 >>> print representation['branch_link']140 http://.../~import-owner/scruff/new-import/+code-import
135 http://.../~import-owner/scruff/new-import141 >>> print representation['branch_link']
136 >>> print representation['rcs_type']142 http://.../~import-owner/scruff/new-import
137 Git143 >>> print representation['rcs_type']
138 >>> print representation['url'] == new_remote_url144 Git
139 True145 >>> print representation['url'] == new_remote_url
140 >>> print representation['cvs_root']146 True
141 None147 >>> print representation['cvs_root']
142 >>> print representation['cvs_module']148 None
143 None149 >>> print representation['cvs_module']
144 >>> print representation['date_last_successful']150 None
145 None151 >>> print representation['date_last_successful']
146152 None
147If we must we can create a CVS import.153
148154If we must we can create a CVS import.
149 >>> product_url = '/' + product.name155
150 >>> new_remote_url = factory.getUniqueURL()156 >>> product_url = '/' + product.name
151 >>> response = import_webservice.named_post(product_url, 'newCodeImport',157 >>> new_remote_url = factory.getUniqueURL()
152 ... branch_name='cvs-import', rcs_type='Concurrent Versions System',158 >>> response = import_webservice.named_post(product_url, 'newCodeImport',
153 ... cvs_root=new_remote_url, cvs_module="foo")159 ... branch_name='cvs-import', rcs_type='Concurrent Versions System',
154 >>> print response.status160 ... cvs_root=new_remote_url, cvs_module="foo")
155 201161 >>> print response.status
156 >>> location = response.getHeader('Location')162 201
157 >>> response = import_webservice.get(location)163 >>> location = response.getHeader('Location')
158 >>> representation = response.jsonBody()164 >>> response = import_webservice.get(location)
159 >>> print representation['self_link']165 >>> representation = response.jsonBody()
160 http://.../~import-owner/scruff/cvs-import/+code-import166 >>> print representation['self_link']
161 >>> print representation['branch_link']167 http://.../~import-owner/scruff/cvs-import/+code-import
162 http://.../~import-owner/scruff/cvs-import168 >>> print representation['branch_link']
163 >>> print representation['rcs_type']169 http://.../~import-owner/scruff/cvs-import
164 Concurrent Versions System170 >>> print representation['rcs_type']
165 >>> print representation['url']171 Concurrent Versions System
166 None172 >>> print representation['url']
167 >>> print representation['cvs_root'] == new_remote_url173 None
168 True174 >>> print representation['cvs_root'] == new_remote_url
169 >>> print representation['cvs_module'] == "foo"175 True
170 True176 >>> print representation['cvs_module'] == "foo"
171 >>> print representation['date_last_successful']177 True
172 None178 >>> print representation['date_last_successful']
173179 None
174We can also create an import targetting a source package.180
175181We can also create an import targetting a source package.
176 >>> source_package_url = (182
177 ... '/' + distribution.name + '/' + distroseries.name + '/+source/'183 >>> source_package_url = (
178 ... + source_package.name)184 ... '/' + distribution.name + '/' + distroseries.name + '/+source/'
179 >>> new_remote_url = factory.getUniqueURL()185 ... + source_package.name)
180 >>> response = import_webservice.named_post(source_package_url,186 >>> new_remote_url = factory.getUniqueURL()
181 ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial',187 >>> response = import_webservice.named_post(source_package_url,
182 ... url=new_remote_url)188 ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial',
183 >>> print response.status189 ... url=new_remote_url)
184 201190 >>> print response.status
185 >>> location = response.getHeader('Location')191 201
186 >>> response = import_webservice.get(location)192 >>> location = response.getHeader('Location')
187 >>> representation = response.jsonBody()193 >>> response = import_webservice.get(location)
188 >>> print representation['self_link']194 >>> representation = response.jsonBody()
189 http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import195 >>> print representation['self_link']
190 >>> print representation['branch_link']196 http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import
191 http://.../~import-owner/scruffbuntu/manic/scruff/new-import197 >>> print representation['branch_link']
192 >>> print representation['rcs_type']198 http://.../~import-owner/scruffbuntu/manic/scruff/new-import
193 Mercurial199 >>> print representation['rcs_type']
194 >>> print representation['url'] == new_remote_url200 Mercurial
195 True201 >>> print representation['url'] == new_remote_url
196 >>> print representation['cvs_root']202 True
197 None203 >>> print representation['cvs_root']
198 >>> print representation['cvs_module']204 None
199 None205 >>> print representation['cvs_module']
200 >>> print representation['date_last_successful']206 None
201 None207 >>> print representation['date_last_successful']
202208 None
203If we wish to create a branch owned by a team we are part of then we can.209
204210If we wish to create a branch owned by a team we are part of then we can.
205 >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team')211
206 >>> new_remote_url = factory.getUniqueURL()212 >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team')
207 >>> response = import_webservice.named_post(product_url, 'newCodeImport',213 >>> new_remote_url = factory.getUniqueURL()
208 ... branch_name='team-import', rcs_type='Git',214 >>> response = import_webservice.named_post(product_url, 'newCodeImport',
209 ... url=new_remote_url, owner=team_url)215 ... branch_name='team-import', rcs_type='Git',
210 >>> print response.status216 ... url=new_remote_url, owner=team_url)
211 201217 >>> print response.status
212 >>> location = response.getHeader('Location')218 201
213 >>> response = import_webservice.get(location)219 >>> location = response.getHeader('Location')
214 >>> representation = response.jsonBody()220 >>> response = import_webservice.get(location)
215 >>> print representation['self_link']221 >>> representation = response.jsonBody()
216 http://.../~import-owner-team/scruff/team-import/+code-import222 >>> print representation['self_link']
217 >>> print representation['branch_link']223 http://.../~import-owner-team/scruff/team-import/+code-import
218 http://.../~import-owner-team/scruff/team-import224 >>> print representation['branch_link']
219 >>> print representation['rcs_type']225 http://.../~import-owner-team/scruff/team-import
220 Git226 >>> print representation['rcs_type']
221 >>> print representation['url'] == new_remote_url227 Git
222 True228 >>> print representation['url'] == new_remote_url
223 >>> print representation['cvs_root']229 True
224 None230 >>> print representation['cvs_root']
225 >>> print representation['cvs_module']231 None
226 None232 >>> print representation['cvs_module']
227 >>> print representation['date_last_successful']233 None
228 None234 >>> print representation['date_last_successful']
235 None
236=======
237 http://svn.domain.com/source
238 >>> print representation['cvs_root']
239 None
240 >>> print representation['cvs_module']
241 None
242 >>> print representation['date_last_successful']
243 None
244
245
246Package Branches
247----------------
248
249The same is true for package branches.
250
251 >>> login(ANONYMOUS)
252 >>> distribution = factory.makeDistribution(name='scruffbuntu')
253 >>> distroseries = factory.makeDistroSeries(
254 ... name='manic', distribution=distribution)
255 >>> source_package = factory.makeSourcePackage(
256 ... sourcepackagename='scruff', distroseries=distroseries)
257 >>> code_import = removeSecurityProxy(factory.makePackageCodeImport(
258 ... registrant=person, sourcepackage=source_package,
259 ... branch_name='import',
260 ... svn_branch_url="http://svn.domain.com/package_source"))
261 >>> logout()
262 >>> import_webservice = webservice_for_person(
263 ... person, permission=OAuthPermission.WRITE_PUBLIC)
264
265There is a link on the branch object
266
267 >>> branch_url = '/' + code_import.branch.unique_name
268 >>> response = import_webservice.get(branch_url)
269 >>> representation = response.jsonBody()
270 >>> print representation['code_import_link']
271 http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import
272
273and there is information available about the import itsef.
274
275 >>> import_url = representation['code_import_link']
276 >>> response = import_webservice.get(import_url)
277 >>> representation = response.jsonBody()
278 >>> print representation['self_link'] == import_url
279 True
280 >>> print representation['branch_link']
281 http://.../~import-owner/scruffbuntu/manic/scruff/import
282 >>> print representation['review_status']
283 Pending Review
284 >>> print representation['rcs_type']
285 Subversion via CSCVS
286 >>> print representation['url']
287 http://svn.domain.com/package_source
288 >>> print representation['cvs_root']
289 None
290 >>> print representation['cvs_module']
291 None
292 >>> print representation['date_last_successful']
293 None
294
295== Creating Imports ==
296
297We can create an import using the API by calling a method on the project.
298
299 >>> product_url = '/' + product.name
300 >>> new_remote_url = factory.getUniqueURL()
301 >>> response = import_webservice.named_post(product_url, 'newCodeImport',
302 ... branch_name='new-import', rcs_type='Git',
303 ... url=new_remote_url)
304 >>> print response.status
305 201
306 >>> location = response.getHeader('Location')
307 >>> response = import_webservice.get(location)
308 >>> representation = response.jsonBody()
309 >>> print representation['self_link']
310 http://.../~import-owner/scruff/new-import/+code-import
311 >>> print representation['branch_link']
312 http://.../~import-owner/scruff/new-import
313 >>> print representation['rcs_type']
314 Git
315 >>> print representation['url'] == new_remote_url
316 True
317 >>> print representation['cvs_root']
318 None
319 >>> print representation['cvs_module']
320 None
321 >>> print representation['date_last_successful']
322 None
323
324If we must we can create a CVS import.
325
326 >>> product_url = '/' + product.name
327 >>> new_remote_url = factory.getUniqueURL()
328 >>> response = import_webservice.named_post(product_url, 'newCodeImport',
329 ... branch_name='cvs-import', rcs_type='Concurrent Versions System',
330 ... cvs_root=new_remote_url, cvs_module="foo")
331 >>> print response.status
332 201
333 >>> location = response.getHeader('Location')
334 >>> response = import_webservice.get(location)
335 >>> representation = response.jsonBody()
336 >>> print representation['self_link']
337 http://.../~import-owner/scruff/cvs-import/+code-import
338 >>> print representation['branch_link']
339 http://.../~import-owner/scruff/cvs-import
340 >>> print representation['rcs_type']
341 Concurrent Versions System
342 >>> print representation['url']
343 None
344 >>> print representation['cvs_root'] == new_remote_url
345 True
346 >>> print representation['cvs_module'] == "foo"
347 True
348 >>> print representation['date_last_successful']
349 None
350
351We can also create an import targetting a source package.
352
353 >>> source_package_url = (
354 ... '/' + distribution.name + '/' + distroseries.name + '/+source/'
355 ... + source_package.name)
356 >>> new_remote_url = factory.getUniqueURL()
357 >>> response = import_webservice.named_post(source_package_url,
358 ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial',
359 ... url=new_remote_url)
360 >>> print response.status
361 201
362 >>> location = response.getHeader('Location')
363 >>> response = import_webservice.get(location)
364 >>> representation = response.jsonBody()
365 >>> print representation['self_link']
366 http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import
367 >>> print representation['branch_link']
368 http://.../~import-owner/scruffbuntu/manic/scruff/new-import
369 >>> print representation['rcs_type']
370 Mercurial
371 >>> print representation['url'] == new_remote_url
372 True
373 >>> print representation['cvs_root']
374 None
375 >>> print representation['cvs_module']
376 None
377 >>> print representation['date_last_successful']
378 None
379
380If we wish to create a branch owned by a team we are part of then we can.
381
382 >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team')
383 >>> new_remote_url = factory.getUniqueURL()
384 >>> response = import_webservice.named_post(product_url, 'newCodeImport',
385 ... branch_name='team-import', rcs_type='Git',
386 ... url=new_remote_url, owner=team_url)
387 >>> print response.status
388 201
389 >>> location = response.getHeader('Location')
390 >>> response = import_webservice.get(location)
391 >>> representation = response.jsonBody()
392 >>> print representation['self_link']
393 http://.../~import-owner-team/scruff/team-import/+code-import
394 >>> print representation['branch_link']
395 http://.../~import-owner-team/scruff/team-import
396 >>> print representation['rcs_type']
397 Git
398 >>> print representation['url'] == new_remote_url
399 True
400 >>> print representation['cvs_root']
401 None
402 >>> print representation['cvs_module']
403 None
404 >>> print representation['date_last_successful']
405 None
406
407
408== Requesting an Import ==
409
410You can request that an approved, working import happen soon over the
411API using the requestImport() method.
412
413 >>> login(ANONYMOUS)
414 >>> git_import = factory.makeProductCodeImport(
415 ... registrant=person, product=product, branch_name='git-import',
416 ... git_repo_url=factory.getUniqueURL())
417 >>> git_import_url = (
418 ... '/' + git_import.branch.unique_name + '/+code-import')
419 >>> logout()
420 >>> import_webservice = webservice_for_person(
421 ... person, permission=OAuthPermission.WRITE_PUBLIC)
422 >>> response = import_webservice.named_post(
423 ... git_import_url, 'requestImport')
424 >>> print response.status
425 200
426 >>> print response.jsonBody()
427 None
428>>>>>>> MERGE-SOURCE
229429
=== modified file 'lib/lp/services/worlddata/doc/language.txt'
--- lib/lp/services/worlddata/doc/language.txt 2010-02-17 10:39:16 +0000
+++ lib/lp/services/worlddata/doc/language.txt 2010-04-16 04:01:13 +0000
@@ -260,8 +260,40 @@
260 Serbian ("Latn" variant)260 Serbian ("Latn" variant)
261261
262262
263translators
264===========
265
266Property `translators` contains the list of `Person`s who are considered
267translators for this language.
268
269 >>> sr = language_set.getLanguageByCode('sr')
270 >>> list(sr.translators)
271 []
272
273To be considered a translator, they must have done some translations and
274have the language among their preferred languages.
275
276 >>> translator = factory.makePerson(name=u'serbian-translator')
277 >>> translator.addLanguage(sr)
278 >>> from canonical.testing import LaunchpadZopelessLayer
279 >>> LaunchpadZopelessLayer.commit()
280
281 # We need to fake some Karma.
282 >>> from lp.registry.model.karma import KarmaCategory, KarmaCache
283 >>> LaunchpadZopelessLayer.switchDbUser('karma')
284 >>> translations_category = KarmaCategory.selectOne(
285 ... KarmaCategory.name=='translations')
286 >>> karma = KarmaCache(person=translator,
287 ... category=translations_category,
288 ... karmavalue=1)
289 >>> LaunchpadZopelessLayer.commit()
290 >>> LaunchpadZopelessLayer.switchDbUser('launchpad')
291 >>> [translator.name for translator in sr.translators]
292 [u'serbian-translator']
293
294
263=========295=========
264countries296Countries
265=========297=========
266298
267Property holding a list of countries a language is spoken in, and allowing299Property holding a list of countries a language is spoken in, and allowing
268300
=== modified file 'lib/lp/services/worlddata/interfaces/language.py'
--- lib/lp/services/worlddata/interfaces/language.py 2010-03-05 14:02:05 +0000
+++ lib/lp/services/worlddata/interfaces/language.py 2010-04-16 04:01:13 +0000
@@ -16,6 +16,7 @@
16from zope.schema import TextLine, Int, Choice, Bool, Field, Set16from zope.schema import TextLine, Int, Choice, Bool, Field, Set
17from zope.interface import Interface, Attribute17from zope.interface import Interface, Attribute
18from lazr.enum import DBEnumeratedType, DBItem18from lazr.enum import DBEnumeratedType, DBItem
19from lazr.lifecycle.snapshot import doNotSnapshot
1920
20from lazr.restful.declarations import (21from lazr.restful.declarations import (
21 collection_default_content, exported, export_as_webservice_collection,22 collection_default_content, exported, export_as_webservice_collection,
@@ -75,9 +76,9 @@
75 required=False),76 required=False),
76 exported_as='plural_expression')77 exported_as='plural_expression')
7778
78 translators = Field(79 translators = doNotSnapshot(Field(
79 title=u'List of Person/Team that translate into this language.',80 title=u'List of Person/Team that translate into this language.',
80 required=True)81 required=True))
8182
82 translators_count = exported(83 translators_count = exported(
83 Int(84 Int(
8485
=== modified file 'lib/lp/services/worlddata/tests/test_doc.py'
--- lib/lp/services/worlddata/tests/test_doc.py 2009-06-30 16:56:07 +0000
+++ lib/lp/services/worlddata/tests/test_doc.py 2010-04-16 04:01:13 +0000
@@ -6,9 +6,20 @@
6"""6"""
77
8import os8import os
9
10from canonical.launchpad.testing.systemdocs import (
11 LayeredDocFileSuite, setUp, tearDown)
12from canonical.testing import LaunchpadZopelessLayer
13
9from lp.services.testing import build_test_suite14from lp.services.testing import build_test_suite
1015
11here = os.path.dirname(os.path.realpath(__file__))16here = os.path.dirname(os.path.realpath(__file__))
17special = {
18 'language.txt': LayeredDocFileSuite(
19 '../doc/language.txt',
20 layer=LaunchpadZopelessLayer,
21 setUp=setUp, tearDown=tearDown),
22 }
1223
13def test_suite():24def test_suite():
14 return build_test_suite(here)25 return build_test_suite(here, special)
1526
=== added file 'lib/lp/services/worlddata/tests/test_language.py'
--- lib/lp/services/worlddata/tests/test_language.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/worlddata/tests/test_language.py 2010-04-16 04:01:13 +0000
@@ -0,0 +1,21 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4__metaclass__ = type
5
6from canonical.testing import FunctionalLayer
7from lazr.lifecycle.interfaces import IDoNotSnapshot
8from lp.services.worlddata.interfaces.language import ILanguage
9from lp.testing import TestCaseWithFactory
10
11
12class TestLanguageWebservice(TestCaseWithFactory):
13 """Test Language web service API."""
14
15 layer = FunctionalLayer
16
17 def test_translators(self):
18 self.failUnless(
19 IDoNotSnapshot.providedBy(ILanguage['translators']),
20 "ILanguage.translators should not be included in snapshots, "
21 "see bug 553093.")
022
=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt 2010-04-08 02:35:06 +0000
+++ lib/lp/soyuz/doc/archive.txt 2010-04-16 04:01:13 +0000
@@ -1407,10 +1407,10 @@
1407 ppa1407 ppa
14081408
1409We can take the opportunity to check if the default 'authorized_size'1409We can take the opportunity to check if the default 'authorized_size'
1410corresponds to what we state in our policy, 1024 MiB:1410corresponds to what we state in our policy, 2048 MiB:
14111411
1412 >>> name16.archive.authorized_size1412 >>> name16.archive.authorized_size
1413 10241413 2048
14141414
1415An archive is also associated with a distribution. This can be found on1415An archive is also associated with a distribution. This can be found on
1416the distribution property. The default distribution is "ubuntu":1416the distribution property. The default distribution is "ubuntu":
14171417
=== modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt'
=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-translations.txt'
--- lib/lp/soyuz/doc/distroseriesqueue-translations.txt 2010-03-12 13:39:33 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue-translations.txt 2010-04-16 04:01:13 +0000
@@ -297,8 +297,35 @@
297297
298 >>> queue_item.customfiles[0].publish(mock_logger)298 >>> queue_item.customfiles[0].publish(mock_logger)
299 DEBUG: Publishing custom pmount, pmount_0.9.7-2ubuntu2_amd64_translations.tar.gz to ubuntu/dapper299 DEBUG: Publishing custom pmount, pmount_0.9.7-2ubuntu2_amd64_translations.tar.gz to ubuntu/dapper
300 DEBUG: Skipping translations since it is a PPA.300 DEBUG: Skipping translations since its purpose is not in
301301 MAIN_ARCHIVE_PURPOSES.
302
303 # And this time, we see that there are no entries imported in the queue.
304 >>> translation_import_queue.getAllEntries(target=ubuntu).count()
305 0
306 >>> transaction.abort()
307
308
309== Translations from a rebuild ==
310
311Translations coming from rebuilt packages are also ignored.
312
313 >>> from lp.registry.interfaces.person import IPersonSet
314 >>> from lp.soyuz.interfaces.archive import ArchivePurpose, IArchiveSet
315
316 >>> foobar_archive = getUtility(IArchiveSet).new(
317 ... purpose=ArchivePurpose.COPY,
318 ... owner=getUtility(IPersonSet).getByName('name16'),
319 ... name='rebuilds')
320
321 >>> dapper = getUtility(IDistributionSet)['ubuntu']['dapper']
322 >>> queue_item = dapper.getQueueItems(status=PackageUploadStatus.NEW)[0]
323 >>> queue_item.archive = foobar_archive
324
325 >>> queue_item.customfiles[0].publish(mock_logger)
326 DEBUG: Publishing custom pmount, pmount_0.9.7-2ubuntu2_amd64_translations.tar.gz to ubuntu/dapper
327 DEBUG: Skipping translations since its purpose is not in
328 MAIN_ARCHIVE_PURPOSES.
302329
303 # And this time, we see that there are no entries imported in the queue.330 # And this time, we see that there are no entries imported in the queue.
304 >>> translation_import_queue.getAllEntries(target=ubuntu).count()331 >>> translation_import_queue.getAllEntries(target=ubuntu).count()
@@ -314,6 +341,8 @@
314 >>> from zope.interface import implements341 >>> from zope.interface import implements
315 >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities342 >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
316 >>> from lp.soyuz.model.queue import PackageUploadCustom343 >>> from lp.soyuz.model.queue import PackageUploadCustom
344 >>> from lp.soyuz.interfaces.archive import (
345 ... IArchive, ArchivePurpose)
317 >>> from lp.soyuz.interfaces.queue import (346 >>> from lp.soyuz.interfaces.queue import (
318 ... IPackageUpload, IPackageUploadCustom)347 ... IPackageUpload, IPackageUploadCustom)
319 >>> from lp.soyuz.interfaces.queue import PackageUploadCustomFormat348 >>> from lp.soyuz.interfaces.queue import PackageUploadCustomFormat
@@ -323,6 +352,11 @@
323 ... ISourcePackageRelease)352 ... ISourcePackageRelease)
324 >>> from lp.registry.interfaces.pocket import PackagePublishingPocket353 >>> from lp.registry.interfaces.pocket import PackagePublishingPocket
325354
355 >>> class MockArchive:
356 ... implements(IArchive)
357 ... def __init__(self, purpose):
358 ... self.purpose = purpose
359
326 >>> class MockSourcePackageRelease:360 >>> class MockSourcePackageRelease:
327 ... implements(ISourcePackageRelease)361 ... implements(ISourcePackageRelease)
328 ... def __init__(self, component, creator):362 ... def __init__(self, component, creator):
@@ -338,14 +372,13 @@
338372
339 >>> class MockPackageUpload:373 >>> class MockPackageUpload:
340 ... implements(IPackageUpload)374 ... implements(IPackageUpload)
341 ... def __init__(self, pocket, auto_sync, sourcepackagerelease):375 ... def __init__(self, pocket, auto_sync, sourcepackagerelease,
376 ... archive):
342 ... self.id = 1377 ... self.id = 1
343 ... self.pocket = pocket378 ... self.pocket = pocket
344 ... self.auto_sync = auto_sync379 ... self.auto_sync = auto_sync
345 ... self.sourcepackagerelease = sourcepackagerelease380 ... self.sourcepackagerelease = sourcepackagerelease
346 ...381 ... self.archive = archive
347 ... def isPPA(self):
348 ... return False
349 ...382 ...
350 ... def isAutoSyncUpload(self, changed_by_email=None):383 ... def isAutoSyncUpload(self, changed_by_email=None):
351 ... return self.auto_sync384 ... return self.auto_sync
@@ -363,10 +396,11 @@
363396
364 >>> katie = getUtility(ILaunchpadCelebrities).katie397 >>> katie = getUtility(ILaunchpadCelebrities).katie
365 >>> release_pocket = PackagePublishingPocket.RELEASE398 >>> release_pocket = PackagePublishingPocket.RELEASE
399 >>> archive = MockArchive(ArchivePurpose.PRIMARY)
366400
367 >>> katie_sourcepackagerelease = MockSourcePackageRelease('main', katie)401 >>> katie_sourcepackagerelease = MockSourcePackageRelease('main', katie)
368 >>> sync_package_upload = MockPackageUpload(402 >>> sync_package_upload = MockPackageUpload(
369 ... release_pocket, True, katie_sourcepackagerelease)403 ... release_pocket, True, katie_sourcepackagerelease, archive)
370 >>> sync_package_upload.isAutoSyncUpload()404 >>> sync_package_upload.isAutoSyncUpload()
371 True405 True
372 >>> translations_upload = MockPackageUploadCustom()406 >>> translations_upload = MockPackageUploadCustom()
@@ -377,7 +411,7 @@
377Non-auto-sync uploads by 'katie' still indicate 'katie' as the uploader.411Non-auto-sync uploads by 'katie' still indicate 'katie' as the uploader.
378412
379 >>> non_sync_package_upload = MockPackageUpload(413 >>> non_sync_package_upload = MockPackageUpload(
380 ... release_pocket, False, katie_sourcepackagerelease)414 ... release_pocket, False, katie_sourcepackagerelease, archive)
381 >>> non_sync_package_upload.isAutoSyncUpload()415 >>> non_sync_package_upload.isAutoSyncUpload()
382 False416 False
383 >>> translations_upload.packageupload = non_sync_package_upload417 >>> translations_upload.packageupload = non_sync_package_upload
@@ -390,7 +424,7 @@
390 >>> carlos = person_set.getByName('carlos')424 >>> carlos = person_set.getByName('carlos')
391 >>> carlos_sourcepackagerelease = MockSourcePackageRelease('main', carlos)425 >>> carlos_sourcepackagerelease = MockSourcePackageRelease('main', carlos)
392 >>> carlos_package_upload = MockPackageUpload(426 >>> carlos_package_upload = MockPackageUpload(
393 ... release_pocket, False, carlos_sourcepackagerelease)427 ... release_pocket, False, carlos_sourcepackagerelease, archive)
394 >>> carlos_package_upload.isAutoSyncUpload()428 >>> carlos_package_upload.isAutoSyncUpload()
395 False429 False
396 >>> translations_upload.packageupload = carlos_package_upload430 >>> translations_upload.packageupload = carlos_package_upload
397431
=== modified file 'lib/lp/soyuz/doc/gina.txt'
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-04-12 08:29:02 +0000
+++ lib/lp/soyuz/model/archive.py 2010-04-16 04:01:13 +0000
@@ -164,7 +164,7 @@
164 dbName='require_virtualized', notNull=True, default=True)164 dbName='require_virtualized', notNull=True, default=True)
165165
166 authorized_size = IntCol(166 authorized_size = IntCol(
167 dbName='authorized_size', notNull=False, default=1024)167 dbName='authorized_size', notNull=False, default=2048)
168168
169 sources_cached = IntCol(169 sources_cached = IntCol(
170 dbName='sources_cached', notNull=False, default=0)170 dbName='sources_cached', notNull=False, default=0)
171171
=== modified file 'lib/lp/soyuz/model/queue.py'
--- lib/lp/soyuz/model/queue.py 2010-04-12 11:37:48 +0000
+++ lib/lp/soyuz/model/queue.py 2010-04-16 04:01:13 +0000
@@ -43,6 +43,7 @@
43from canonical.database.sqlbase import SQLBase, sqlvalues43from canonical.database.sqlbase import SQLBase, sqlvalues
44from canonical.encoding import guess as guess_encoding, ascii_smash44from canonical.encoding import guess as guess_encoding, ascii_smash
45from canonical.launchpad.helpers import get_email_template45from canonical.launchpad.helpers import get_email_template
46from lp.soyuz.interfaces.archive import MAIN_ARCHIVE_PURPOSES
46from lp.soyuz.interfaces.binarypackagerelease import (47from lp.soyuz.interfaces.binarypackagerelease import (
47 BinaryPackageFormat)48 BinaryPackageFormat)
48from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities49from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
@@ -1701,9 +1702,11 @@
1701 """See `IPackageUploadCustom`."""1702 """See `IPackageUploadCustom`."""
1702 sourcepackagerelease = self.packageupload.sourcepackagerelease1703 sourcepackagerelease = self.packageupload.sourcepackagerelease
17031704
1704 # Ignore translation coming from PPA.1705 # Ignore translations not with main distribution purposes.
1705 if self.packageupload.isPPA():1706 if self.packageupload.archive.purpose not in MAIN_ARCHIVE_PURPOSES:
1706 debug(logger, "Skipping translations since it is a PPA.")1707 debug(logger,
1708 "Skipping translations since its purpose is not "
1709 "in MAIN_ARCHIVE_PURPOSES.")
1707 return1710 return
17081711
1709 valid_pockets = (1712 valid_pockets = (
17101713
=== modified file 'lib/lp/testing/factory.py'
=== modified file 'lib/lp/translations/templates/language-index.pt'
--- lib/lp/translations/templates/language-index.pt 2009-11-27 14:18:05 +0000
+++ lib/lp/translations/templates/language-index.pt 2010-04-16 04:01:13 +0000
@@ -66,36 +66,17 @@
66 being experts in66 being experts in
67 <tal:language replace="view/language_name">67 <tal:language replace="view/language_name">
68 Espa&ntilde;ol68 Espa&ntilde;ol
69 </tal:language>69 </tal:language>:
70 :
71 </p>70 </p>
72 <table>71 <div tal:repeat="expert_info view/translation_teams">
73 <tr tal:repeat="expert_info view/translation_teams">72 <a tal:replace="structure expert_info/expert/fmt:link">Person</a>
74 <td>73 (<tal:groups repeat="group expert_info/groups"
75 <img tal:condition="expert_info/expert/isTeam"74 ><a tal:replace="structure group/fmt:link"
76 src="/@@/team" />75 >Translation group title</a
77 <img tal:condition="not:expert_info/expert/isTeam"76 ><tal:comma
78 src="/@@/person" />77 condition="not:repeat/group/end">, </tal:comma
79 </td>78 ></tal:groups>)
80 <td>79 </div>
81 <div>
82 <a
83 tal:attributes="
84 href expert_info/expert/fmt:url;
85 title expert_info/expert/displayname/fmt:shorten/80"
86 tal:content="expert_info/expert/displayname"
87 >Expert name</a>(
88 <tal:groups repeat="group expert_info/groups">
89 <a tal:attributes="
90 href group/fmt:url;
91 title group/title/fmt:shorten/80"
92 tal:content="group/title"
93 >Translation group title</a>
94 </tal:groups>)
95 </div>
96 </td>
97 </tr>
98 </table>
99 <p tal:condition="not:view/translation_teams">80 <p tal:condition="not:view/translation_teams">
100 <tal:language replace="view/language_name">81 <tal:language replace="view/language_name">
101 Espa&ntilde;ol82 Espa&ntilde;ol

Subscribers

People subscribed via source and target branches

to status/vote changes: