Merge lp:~jtv/launchpad/branchrevision into lp:launchpad/db-devel

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: 9539
Proposed branch: lp:~jtv/launchpad/branchrevision
Merge into: lp:launchpad/db-devel
Diff against target: 1001 lines (+221/-168)
11 files modified
lib/lp/code/doc/branch.txt (+30/-24)
lib/lp/code/doc/revision.txt (+26/-18)
lib/lp/code/interfaces/branch.py (+14/-11)
lib/lp/code/interfaces/branchrevision.py (+0/-1)
lib/lp/code/model/branch.py (+36/-32)
lib/lp/code/model/branchmergeproposal.py (+43/-30)
lib/lp/code/model/branchrevision.py (+30/-17)
lib/lp/code/model/revision.py (+4/-4)
lib/lp/code/model/tests/test_branchjob.py (+14/-15)
lib/lp/codehosting/scanner/tests/test_bzrsync.py (+19/-12)
lib/lp/services/database/prejoin.py (+5/-4)
To merge this branch: bzr merge lp:~jtv/launchpad/branchrevision
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Review via email: mp+29791@code.launchpad.net

Commit message

Stormify BranchRevision.

Description of the change

= Stormify BranchRevision =

This branch is part of an effort to eliminate the id field from BranchRevision. The table is too large and ids are not far from running out. (The alternative is to expand the field and make the table even larger). The branch is against db-devel, since this is part of work that will affect the schema. We probably should consider landing it against devel. But all that happens here is to stormify the class and the queries that mention it.

To test, I ran all unit and doc tests for Code (including browser & model unit tests).

There is a bit of pre-existing lint involving tuples like (foo,) not having a space after the comma. I think the existing spelling is appropriate and I'll discuss possible changes to the checker with Curtis.

Jeroen

To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (11.4 KiB)

Hi Jeroen,

This is a nice improvement. Comments below.

-Edwin

>=== modified file 'lib/lp/code/interfaces/branchrevision.py'
>--- lib/lp/code/interfaces/branchrevision.py 2009-06-25 04:06:00 +0000
>+++ lib/lp/code/interfaces/branchrevision.py 2010-07-13 13:32:27 +0000
>@@ -21,7 +21,7 @@
> ancestry of a branch. History revisions have an integer sequence, merged
> revisions have sequence set to None.
> """
>-
>+ # XXX JeroenVermeulen 2010-07-12: We're dropping this column.

I assume you are planning on adding a bug for this XXX.

> id = Int(title=_('The database revision ID'))
>
> sequence = Int(
>
>=== modified file 'lib/lp/code/model/branch.py'
>--- lib/lp/code/model/branch.py 2010-07-09 10:22:32 +0000
>+++ lib/lp/code/model/branch.py 2010-07-13 13:32:27 +0000
>@@ -226,11 +226,12 @@
>
> @property
> def revision_history(self):
>- return BranchRevision.select('''
>- BranchRevision.branch = %s AND
>- BranchRevision.sequence IS NOT NULL
>- ''' % sqlvalues(self),
>- prejoins=['revision'], orderBy='-sequence')
>+ # XXX JeroenVermeulen 2010-07-12: Prejoin revision.
>+ result = Store.of(self).find(
>+ BranchRevision,
>+ BranchRevision.branch_id == self.id,
>+ BranchRevision.sequence != None)
>+ return result.order_by(Desc(BranchRevision.sequence))

You can prejoin the revision like this. The Revision will be added
to the cache, and since BranchRevision has the foreign key, it will
retrieve the Revision from the cache by id, even though the list
comprehension appears to throw it away.

> result = Store.of(self).find(
> (BranchRevision, Revision),
> BranchRevision.revisionID == Revision.id,
> BranchRevision.branch_id == self.id,
> BranchRevision.sequence != None)
> result = result.order_by(Desc(BranchRevision.sequence))
> return [branch_revision for branch_revision, branch in result]
>
>
> subscriptions = SQLMultipleJoin(
> 'BranchSubscription', joinColumn='branch', orderBy='id')
>@@ -509,7 +510,7 @@
>
> def latest_revisions(self, quantity=10):
> """See `IBranch`."""
>- return self.revision_history.limit(quantity)
>+ return self.revision_history.config(limit=quantity)

Of course, if you use the prejoin above, you will need access to
the storm query before stripping out the Revision.

> def getMainlineBranchRevisions(self, start_date, end_date=None,
> oldest_first=False):
>@@ -532,14 +533,13 @@
>
> def getRevisionsSince(self, timestamp):
> """See `IBranch`."""
>- return BranchRevision.select(
>- 'Revision.id=BranchRevision.revision AND '
>- 'BranchRevision.branch = %d AND '
>- 'BranchRevision.sequence IS NOT NULL AND '
>- 'Revision.revision_date > %s' %
>- (self.id, quote(timestamp)),
>- orderBy='-sequence',
>- clauseTables=['Revision'])
>+ result = Store.of(self).using(Revision).find(
>+ BranchRevision,
>+ Revision == BranchRevision.revision,
>...

review: Approve (code)
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (7.3 KiB)

Thanks for a particularly thoughtful and helpful review.

> >=== modified file 'lib/lp/code/interfaces/branchrevision.py'
> >--- lib/lp/code/interfaces/branchrevision.py 2009-06-25 04:06:00 +0000
> >+++ lib/lp/code/interfaces/branchrevision.py 2010-07-13 13:32:27 +0000
> >@@ -21,7 +21,7 @@
> > ancestry of a branch. History revisions have an integer sequence, merged
> > revisions have sequence set to None.
> > """
> >-
> >+ # XXX JeroenVermeulen 2010-07-12: We're dropping this column.
>
>
> I assume you are planning on adding a bug for this XXX.

This was really just a transient development note; I've dropped it so that we don't leave unnecessary cruft lying around.

> >=== modified file 'lib/lp/code/model/branch.py'
> >--- lib/lp/code/model/branch.py 2010-07-09 10:22:32 +0000
> >+++ lib/lp/code/model/branch.py 2010-07-13 13:32:27 +0000

> >+ result = Store.of(self).find(
> >+ BranchRevision,
> >+ BranchRevision.branch_id == self.id,
> >+ BranchRevision.sequence != None)
> >+ return result.order_by(Desc(BranchRevision.sequence))
>
>
> You can prejoin the revision like this. The Revision will be added
> to the cache, and since BranchRevision has the foreign key, it will
> retrieve the Revision from the cache by id, even though the list
> comprehension appears to throw it away.

I used Stuart's prejoin helper from lp.services.database. I cleaned that up a bit as well, and corrected the documentation.

> >@@ -509,7 +510,7 @@
> >
> > def latest_revisions(self, quantity=10):
> > """See `IBranch`."""
> >- return self.revision_history.limit(quantity)
> >+ return self.revision_history.config(limit=quantity)
>
>
> Of course, if you use the prejoin above, you will need access to
> the storm query before stripping out the Revision.

Thankfully the prejoin helper takes care of that!

> >@@ -532,14 +533,13 @@
> >
> > def getRevisionsSince(self, timestamp):
> > """See `IBranch`."""
> >- return BranchRevision.select(
> >- 'Revision.id=BranchRevision.revision AND '
> >- 'BranchRevision.branch = %d AND '
> >- 'BranchRevision.sequence IS NOT NULL AND '
> >- 'Revision.revision_date > %s' %
> >- (self.id, quote(timestamp)),
> >- orderBy='-sequence',
> >- clauseTables=['Revision'])
> >+ result = Store.of(self).using(Revision).find(
> >+ BranchRevision,
> >+ Revision == BranchRevision.revision,
> >+ BranchRevision.branch == self,
> >+ BranchRevision.sequence != None,
> >+ Revision.revision_date > timestamp)
> >+ return result.order_by(Desc(BranchRevision.sequence))
>
> A prejoin would be good here, too.

Done.

> >@@ -860,8 +860,8 @@
> >
> > def getScannerData(self):
> > """See `IBranch`."""
> >- cur = cursor()
> >- cur.execute("""
> >+# XXX JeroenVermeulen 2010-07-12: We're dropping BranchRevision.id.
>
>
> This XXX needs that bug number and some indentation.

Removed as above.

> >+ rows = Store.of(self).execute("""
>
>
> Storm queries can retriev...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/doc/branch.txt'
--- lib/lp/code/doc/branch.txt 2010-05-27 02:09:13 +0000
+++ lib/lp/code/doc/branch.txt 2010-07-14 16:19:43 +0000
@@ -158,10 +158,10 @@
158158
159== Determining the recently changed, registered and imported branches ==159== Determining the recently changed, registered and imported branches ==
160160
161The IBranchSet methods getRecentlyChangedBranches, getRecentlyImportedBranches,161The IBranchSet methods getRecentlyChangedBranches,
162and getRecentlyRegisteredBranches are used to give summary information that162getRecentlyImportedBranches, and getRecentlyRegisteredBranches are used to
163is to be displayed on the code.launchpad.net page to entice the163give summary information that is to be displayed on the code.launchpad.net
164user to click through.164page to entice the user to click through.
165165
166Changed branches are branches that are owned by real people or groups (as166Changed branches are branches that are owned by real people or groups (as
167opposed to vcs-imports), and have recently had new revisions detected by167opposed to vcs-imports), and have recently had new revisions detected by
@@ -315,8 +315,8 @@
315 True315 True
316 >>> subscription.branch == branch and subscription.person == subscriber316 >>> subscription.branch == branch and subscription.person == subscriber
317 True317 True
318 >>> subscription.notification_level == BranchSubscriptionNotificationLevel.FULL318 >>> print subscription.notification_level.name
319 True319 FULL
320 >>> subscription.max_diff_lines == BranchSubscriptionDiffSize.FIVEKLINES320 >>> subscription.max_diff_lines == BranchSubscriptionDiffSize.FIVEKLINES
321 True321 True
322 >>> subscription.review_level == CodeReviewNotificationLevel.FULL322 >>> subscription.review_level == CodeReviewNotificationLevel.FULL
@@ -327,7 +327,7 @@
327 True327 True
328 >>> from canonical.launchpad.webapp import canonical_url328 >>> from canonical.launchpad.webapp import canonical_url
329 >>> print canonical_url(subscription)329 >>> print canonical_url(subscription)
330 http://code.launchpad.dev/~user/product/subscribed/+subscription/subscriber330 http://code...dev/~user/product/subscribed/+subscription/subscriber
331331
332The settings for a subscription can be changed by re-subscribing.332The settings for a subscription can be changed by re-subscribing.
333333
@@ -399,8 +399,8 @@
399399
400Branch.revision_history gives the sequence of revisions in this branch's400Branch.revision_history gives the sequence of revisions in this branch's
401history, latest revisions first. All revision history items must implement the401history, latest revisions first. All revision history items must implement the
402IBranchRevision interface. The Branch.revision_count attribute gives the length402IBranchRevision interface. The Branch.revision_count attribute gives the
403of the revision_history attribute but without building the list.403length of the revision_history attribute but without building the list.
404404
405 >>> from lp.code.interfaces.branchrevision import IBranchRevision405 >>> from lp.code.interfaces.branchrevision import IBranchRevision
406 >>> junk.revision_count406 >>> junk.revision_count
@@ -427,11 +427,11 @@
427 True427 True
428428
429Branch.getRevisionsSince gives all the BranchRevisions for revisions committed429Branch.getRevisionsSince gives all the BranchRevisions for revisions committed
430since a given timestamp. It may give surprising results if some committers had a430since a given timestamp. It may give surprising results if some committers had
431skewed clock.431a skewed clock.
432432
433 >>> from datetime import datetime433 >>> from datetime import datetime
434 >>> timestamp = datetime(2005, 10, 31, 12, 00, 00)434 >>> timestamp = datetime(2005, 10, 31, 12, 00, 00, 0, pytz.UTC)
435 >>> two_latest = list(junk.revision_history)[:2]435 >>> two_latest = list(junk.revision_history)[:2]
436 >>> list(junk.getRevisionsSince(timestamp)) == two_latest436 >>> list(junk.getRevisionsSince(timestamp)) == two_latest
437 True437 True
@@ -440,10 +440,11 @@
440== Ancestry of Revision ==440== Ancestry of Revision ==
441441
442The revision-history of a given branch, is only one possible ancestry path in442The revision-history of a given branch, is only one possible ancestry path in
443the ancestry graph. It is also possible to examine the ancestry graph directly.443the ancestry graph. It is also possible to examine the ancestry graph
444directly.
444445
445A Bazaar branch may contains references (by revision_id) to revisions for which446A Bazaar branch may contains references (by revision_id) to revisions for
446no data is available. Such revisions are called "ghosts".447which no data is available. Such revisions are called "ghosts".
447448
448Initial commits (after a "bzr init") revisions have no parent.449Initial commits (after a "bzr init") revisions have no parent.
449450
@@ -452,8 +453,8 @@
452 >>> initial.parent_ids453 >>> initial.parent_ids
453 []454 []
454455
455Normal commits (as opposed to merges) have exactly one parent. The first parent456Normal commits (as opposed to merges) have exactly one parent. The first
456of a revision is always the revision that was current when committing.457parent of a revision is always the revision that was current when committing.
457458
458 >>> commit = history[-2].revision459 >>> commit = history[-2].revision
459 >>> [type(a) for a in commit.parent_ids] == [unicode]460 >>> [type(a) for a in commit.parent_ids] == [unicode]
@@ -492,8 +493,8 @@
492is identified by the branch attribute "last_scanned_id". This is the textual493is identified by the branch attribute "last_scanned_id". This is the textual
493revision_id for the bzr revision. The reason that it is a text id rather than494revision_id for the bzr revision. The reason that it is a text id rather than
494an integer foreign key is so it can easily be compared to the495an integer foreign key is so it can easily be compared to the
495"last_mirrored_id". The "last_mirrored_id" is set by the branch puller, and is496"last_mirrored_id". The "last_mirrored_id" is set by the branch puller, and
496used to identify when a scan is needed for a branch.497is used to identify when a scan is needed for a branch.
497498
498 >>> branch = branch_lookup.get(1)499 >>> branch = branch_lookup.get(1)
499 >>> branch.last_scanned_id500 >>> branch.last_scanned_id
@@ -504,7 +505,8 @@
504 >>> branch.getTipRevision() is None505 >>> branch.getTipRevision() is None
505 True506 True
506507
507 >>> branch.last_scanned_id = 'test@canonical.com-20051031165248-6f1bb97973c2b4f4'508 >>> branch.last_scanned_id = (
509 ... 'test@canonical.com-20051031165248-6f1bb97973c2b4f4')
508 >>> rev = branch.getTipRevision()510 >>> rev = branch.getTipRevision()
509 >>> print rev.date_created511 >>> print rev.date_created
510 2005-10-31 17:21:47.381770+00:00512 2005-10-31 17:21:47.381770+00:00
@@ -560,7 +562,8 @@
560562
561== Deleting branches ==563== Deleting branches ==
562564
563If a user creates a branch in error, they should be able to remove that branch.565If a user creates a branch in error, they should be able to remove that
566branch.
564567
565A branch can be deleted trivially if it is not associated with any bugs or568A branch can be deleted trivially if it is not associated with any bugs or
566blueprints, has no subscribers, and hasn't been associated with any product569blueprints, has no subscribers, and hasn't been associated with any product
@@ -585,9 +588,12 @@
585 >>> cur = cursor()588 >>> cur = cursor()
586 >>> references = list(postgresql.listReferences(cur, 'branch', 'id'))589 >>> references = list(postgresql.listReferences(cur, 'branch', 'id'))
587590
588 >>> for name in sorted([591 >>> listing = sorted([
589 ... '%s.%s' % (src_tab, src_col) for592 ... '%s.%s' % (src_tab, src_col)
590 ... src_tab, src_col, ref_tab, ref_col, updact, delact in references]):593 ... for src_tab, src_col, ref_tab, ref_col, updact, delact
594 ... in references
595 ... ])
596 >>> for name in listing:
591 ... print name597 ... print name
592 branch.stacked_on598 branch.stacked_on
593 branchjob.branch599 branchjob.branch
594600
=== modified file 'lib/lp/code/doc/revision.txt'
--- lib/lp/code/doc/revision.txt 2009-06-16 03:31:05 +0000
+++ lib/lp/code/doc/revision.txt 2010-07-14 16:19:43 +0000
@@ -1,11 +1,12 @@
1= Bazaar Revisions =1= Bazaar Revisions =
22
3Branches are collection of revisions, and a revision can exist independently3Branches are collection of revisions, and a revision can exist independently
4from any branch. Revisions are created automatically by scanning branches, they4from any branch. Revisions are created automatically by scanning branches,
5have no creation interface and Launchpad cannot create or modify them.5they have no creation interface and Launchpad cannot create or modify them.
66
7== Interfaces ==7== Interfaces ==
88
9 >>> from canonical.launchpad.interfaces.lpstorm import IStore
9 >>> from canonical.launchpad.webapp.testing import verifyObject10 >>> from canonical.launchpad.webapp.testing import verifyObject
10 >>> from lp.code.interfaces.revision import (11 >>> from lp.code.interfaces.revision import (
11 ... IRevision, IRevisionAuthor, IRevisionParent, IRevisionSet)12 ... IRevision, IRevisionAuthor, IRevisionParent, IRevisionSet)
@@ -21,14 +22,16 @@
21 True22 True
22 >>> verifyObject(IRevisionSet, RevisionSet())23 >>> verifyObject(IRevisionSet, RevisionSet())
23 True24 True
24 >>> verifyObject(IBranchRevision, BranchRevision.get(1))25 >>> verifyObject(
26 ... IBranchRevision,
27 ... IStore(BranchRevision).find(BranchRevision).any())
25 True28 True
2629
27== Creating revisions ==30== Creating revisions ==
2831
29The creator of a revision is identified by a RevisionAuthor. A RevisionAuthor32The creator of a revision is identified by a RevisionAuthor. A RevisionAuthor
30is not a person because that is only an informational attribute, and even if we33is not a person because that is only an informational attribute, and even if
31trust it, there's really no simple way to map that reliably to persons.34we trust it, there's really no simple way to map that reliably to persons.
3235
33 >>> from lp.code.model.revision import RevisionAuthor36 >>> from lp.code.model.revision import RevisionAuthor
34 >>> author = RevisionAuthor(name='ddaa@localhost')37 >>> author = RevisionAuthor(name='ddaa@localhost')
@@ -55,7 +58,8 @@
55 >>> date = datetime(2005, 3, 8, 12, 0, tzinfo=UTC)58 >>> date = datetime(2005, 3, 8, 12, 0, tzinfo=UTC)
56 >>> from lp.code.model.revision import Revision59 >>> from lp.code.model.revision import Revision
57 >>> revision_1 = Revision(log_body=log_body_1,60 >>> revision_1 = Revision(log_body=log_body_1,
58 ... revision_author=author, revision_id=revision_id_1, revision_date=date)61 ... revision_author=author, revision_id=revision_id_1,
62 ... revision_date=date)
5963
60== Parents ==64== Parents ==
6165
@@ -67,7 +71,8 @@
67we can represent revisions whose at least one parent is a ghost revision.71we can represent revisions whose at least one parent is a ghost revision.
6872
69 >>> revision_2 = Revision(log_body=log_body_2,73 >>> revision_2 = Revision(log_body=log_body_2,
70 ... revision_author=author, revision_id=revision_id_2, revision_date=date)74 ... revision_author=author, revision_id=revision_id_2,
75 ... revision_date=date)
7176
72 >>> from lp.code.model.revision import RevisionParent77 >>> from lp.code.model.revision import RevisionParent
73 >>> rev2_parent = RevisionParent(sequence=0, revision=revision_2,78 >>> rev2_parent = RevisionParent(sequence=0, revision=revision_2,
@@ -86,8 +91,10 @@
8691
87BranchRevision rows are created using `Branch.createBranchRevision`.92BranchRevision rows are created using `Branch.createBranchRevision`.
8893
89 >>> rev_no_1 = branch.createBranchRevision(sequence=1, revision=revision_1)94 >>> rev_no_1 = branch.createBranchRevision(
90 >>> rev_no_2 = branch.createBranchRevision(sequence=2, revision=revision_2)95 ... sequence=1, revision=revision_1)
96 >>> rev_no_2 = branch.createBranchRevision(
97 ... sequence=2, revision=revision_2)
91 >>> rev_no_1.branch == rev_no_2.branch == branch98 >>> rev_no_1.branch == rev_no_2.branch == branch
92 True99 True
93100
@@ -96,13 +103,14 @@
96 >>> branch = getUtility(IBranchLookup).getByUniqueName(103 >>> branch = getUtility(IBranchLookup).getByUniqueName(
97 ... '~name12/+junk/junk.contrib')104 ... '~name12/+junk/junk.contrib')
98105
99The full ancestry of a branch is recorded. That includes the history commits on106The full ancestry of a branch is recorded. That includes the history commits
100this branch, but also revisions that were merged into this branch. Such merged107on this branch, but also revisions that were merged into this branch. Such
101revisions are associated to the branch using BranchRevision whose sequence108merged revisions are associated to the branch using BranchRevision whose
102attribute is None.109sequence attribute is None.
103110
104 >>> from lp.code.model.branchrevision import BranchRevision111 >>> from lp.code.model.branchrevision import BranchRevision
105 >>> ancestry = BranchRevision.selectBy(branch=branch)112 >>> ancestry = IStore(BranchRevision).find(
113 ... BranchRevision, BranchRevision.branch == branch)
106 >>> for branch_revision in sorted(ancestry,114 >>> for branch_revision in sorted(ancestry,
107 ... key=lambda r:(r.sequence, r.revision.id), reverse=True):115 ... key=lambda r:(r.sequence, r.revision.id), reverse=True):
108 ... print branch_revision.sequence, branch_revision.revision.id116 ... print branch_revision.sequence, branch_revision.revision.id
@@ -116,16 +124,16 @@
116 None 6124 None 6
117125
118If you need to operate on the ancestry of a branch, you should write a focused126If you need to operate on the ancestry of a branch, you should write a focused
119query to avoid creating the tens of thousands of objects necessary to represent127query to avoid creating the tens of thousands of objects necessary to
120the ancestry of a large branch.128represent the ancestry of a large branch.
121129
122In particular, IBranch.getScannerData efficiently retrieves the BranchRevision130In particular, IBranch.getScannerData efficiently retrieves the BranchRevision
123data needed by the branch-scanner script.131data needed by the branch-scanner script.
124132
125 >>> ancestry, history, mapping = branch.getScannerData()133 >>> ancestry, history, mapping = branch.getScannerData()
126134
127The first return value is a set of revision_id strings for the full ancestry of135The first return value is a set of revision_id strings for the full ancestry
128the branch.136of the branch.
129137
130 >>> for revision_id in sorted(ancestry):138 >>> for revision_id in sorted(ancestry):
131 ... print revision_id139 ... print revision_id
132140
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py 2010-07-09 10:22:32 +0000
+++ lib/lp/code/interfaces/branch.py 2010-07-14 16:19:43 +0000
@@ -463,9 +463,9 @@
463 is set, the branch gets moved into the junk namespace of the branch463 is set, the branch gets moved into the junk namespace of the branch
464 owner.464 owner.
465465
466 :raise: `BranchTargetError` if both project and source_package are set,466 :raise: `BranchTargetError` if both project and source_package are
467 or if either the project or source_package fail to be adapted to an467 set, or if either the project or source_package fail to be adapted
468 IBranchTarget.468 to an `IBranchTarget`.
469 """469 """
470470
471 reviewer = exported(471 reviewer = exported(
@@ -668,12 +668,14 @@
668668
669 # Joins669 # Joins
670 revision_history = Attribute(670 revision_history = Attribute(
671 """The sequence of BranchRevision for the mainline of that branch.671 """The sequence of revisions for the mainline of this branch.
672672
673 They are ordered with the most recent revision first, and the list673 They are ordered with the most recent revision first, and the list
674 only contains those in the "leftmost tree", or in other words674 only contains those in the "leftmost tree", or in other words
675 the revisions that match the revision history from bzrlib for this675 the revisions that match the revision history from bzrlib for this
676 branch.676 branch.
677
678 The revisions are listed as tuples of (`BranchRevision`, `Revision`).
677 """)679 """)
678 subscriptions = exported(680 subscriptions = exported(
679 CollectionField(681 CollectionField(
@@ -703,7 +705,7 @@
703 def destroySelfBreakReferences():705 def destroySelfBreakReferences():
704 """Delete the specified branch.706 """Delete the specified branch.
705707
706 BranchRevisions associated with this branch will also be deleted as 708 BranchRevisions associated with this branch will also be deleted as
707 well as any items with mandatory references.709 well as any items with mandatory references.
708 """710 """
709711
@@ -766,8 +768,7 @@
766 title=_('Commit message'),768 title=_('Commit message'),
767 description=_('Message to use when committing this merge.')),769 description=_('Message to use when committing this merge.')),
768 reviewers=List(value_type=Reference(schema=IPerson)),770 reviewers=List(value_type=Reference(schema=IPerson)),
769 review_types=List(value_type=TextLine())771 review_types=List(value_type=TextLine()))
770 )
771 # target_branch and prerequisite_branch are actually IBranch, patched in772 # target_branch and prerequisite_branch are actually IBranch, patched in
772 # _schema_circular_imports.773 # _schema_circular_imports.
773 @call_with(registrant=REQUEST_USER)774 @call_with(registrant=REQUEST_USER)
@@ -782,7 +783,8 @@
782 Both the target_branch and the prerequisite_branch, if it is there,783 Both the target_branch and the prerequisite_branch, if it is there,
783 must be branches with the same target as the source branch.784 must be branches with the same target as the source branch.
784785
785 Personal branches (a.k.a. junk branches) cannot specify landing targets.786 Personal branches (a.k.a. junk branches) cannot specify landing
787 targets.
786 """788 """
787789
788 def addLandingTarget(registrant, target_branch, prerequisite_branch=None,790 def addLandingTarget(registrant, target_branch, prerequisite_branch=None,
@@ -794,7 +796,8 @@
794 Both the target_branch and the prerequisite_branch, if it is there,796 Both the target_branch and the prerequisite_branch, if it is there,
795 must be branches with the same target as the source branch.797 must be branches with the same target as the source branch.
796798
797 Personal branches (a.k.a. junk branches) cannot specify landing targets.799 Personal branches (a.k.a. junk branches) cannot specify landing
800 targets.
798801
799 :param registrant: The person who is adding the landing target.802 :param registrant: The person who is adding the landing target.
800 :param target_branch: Must be another branch, and different to self.803 :param target_branch: Must be another branch, and different to self.
@@ -949,8 +952,8 @@
949 project is accessible using:952 project is accessible using:
950 lp:fooix - the linked object is the product fooix953 lp:fooix - the linked object is the product fooix
951 lp:fooix/trunk - the linked object is the trunk series of fooix954 lp:fooix/trunk - the linked object is the trunk series of fooix
952 lp:~owner/fooix/name - the unique name of the branch where the linked955 lp:~owner/fooix/name - the unique name of the branch where the
953 object is the branch itself.956 linked object is the branch itself.
954 """957 """
955958
956 # subscription-related methods959 # subscription-related methods
957960
=== modified file 'lib/lp/code/interfaces/branchrevision.py'
--- lib/lp/code/interfaces/branchrevision.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/interfaces/branchrevision.py 2010-07-14 16:19:43 +0000
@@ -21,7 +21,6 @@
21 ancestry of a branch. History revisions have an integer sequence, merged21 ancestry of a branch. History revisions have an integer sequence, merged
22 revisions have sequence set to None.22 revisions have sequence set to None.
23 """23 """
24
25 id = Int(title=_('The database revision ID'))24 id = Int(title=_('The database revision ID'))
2625
27 sequence = Int(26 sequence = Int(
2827
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2010-07-09 10:22:32 +0000
+++ lib/lp/code/model/branch.py 2010-07-14 16:19:43 +0000
@@ -34,7 +34,7 @@
34from canonical.config import config34from canonical.config import config
35from canonical.database.constants import DEFAULT, UTC_NOW35from canonical.database.constants import DEFAULT, UTC_NOW
36from canonical.database.sqlbase import (36from canonical.database.sqlbase import (
37 cursor, quote, SQLBase, sqlvalues)37 SQLBase, sqlvalues)
38from canonical.database.datetimecol import UtcDateTimeCol38from canonical.database.datetimecol import UtcDateTimeCol
39from canonical.database.enumcol import EnumCol39from canonical.database.enumcol import EnumCol
4040
@@ -79,6 +79,7 @@
79from lp.codehosting.bzrutils import safe_open79from lp.codehosting.bzrutils import safe_open
80from lp.registry.interfaces.person import (80from lp.registry.interfaces.person import (
81 validate_person_not_private_membership, validate_public_person)81 validate_person_not_private_membership, validate_public_person)
82from lp.services.database.prejoin import prejoin
82from lp.services.job.interfaces.job import JobStatus83from lp.services.job.interfaces.job import JobStatus
83from lp.services.mail.notificationrecipientset import (84from lp.services.mail.notificationrecipientset import (
84 NotificationRecipientSet)85 NotificationRecipientSet)
@@ -226,11 +227,13 @@
226227
227 @property228 @property
228 def revision_history(self):229 def revision_history(self):
229 return BranchRevision.select('''230 result = Store.of(self).find(
230 BranchRevision.branch = %s AND231 (BranchRevision, Revision),
231 BranchRevision.sequence IS NOT NULL232 BranchRevision.branch_id == self.id,
232 ''' % sqlvalues(self),233 Revision.id == BranchRevision.revision_id,
233 prejoins=['revision'], orderBy='-sequence')234 BranchRevision.sequence != None)
235 result = result.order_by(Desc(BranchRevision.sequence))
236 return prejoin(result, return_slice=slice(0, 1))
234237
235 subscriptions = SQLMultipleJoin(238 subscriptions = SQLMultipleJoin(
236 'BranchSubscription', joinColumn='branch', orderBy='id')239 'BranchSubscription', joinColumn='branch', orderBy='id')
@@ -450,7 +453,7 @@
450 @property453 @property
451 def code_is_browseable(self):454 def code_is_browseable(self):
452 """See `IBranch`."""455 """See `IBranch`."""
453 return (self.revision_count > 0 or self.last_mirrored != None)456 return (self.revision_count > 0 or self.last_mirrored != None)
454457
455 def codebrowse_url(self, *extras):458 def codebrowse_url(self, *extras):
456 """See `IBranch`."""459 """See `IBranch`."""
@@ -509,7 +512,7 @@
509512
510 def latest_revisions(self, quantity=10):513 def latest_revisions(self, quantity=10):
511 """See `IBranch`."""514 """See `IBranch`."""
512 return self.revision_history.limit(quantity)515 return self.revision_history.config(limit=quantity)
513516
514 def getMainlineBranchRevisions(self, start_date, end_date=None,517 def getMainlineBranchRevisions(self, start_date, end_date=None,
515 oldest_first=False):518 oldest_first=False):
@@ -532,14 +535,15 @@
532535
533 def getRevisionsSince(self, timestamp):536 def getRevisionsSince(self, timestamp):
534 """See `IBranch`."""537 """See `IBranch`."""
535 return BranchRevision.select(538 result = Store.of(self).find(
536 'Revision.id=BranchRevision.revision AND '539 (BranchRevision, Revision),
537 'BranchRevision.branch = %d AND '540 Revision.id == BranchRevision.revision_id,
538 'BranchRevision.sequence IS NOT NULL AND '541 BranchRevision.branch == self,
539 'Revision.revision_date > %s' %542 BranchRevision.sequence != None,
540 (self.id, quote(timestamp)),543 Revision.revision_date > timestamp)
541 orderBy='-sequence',544 result = result.order_by(Desc(BranchRevision.sequence))
542 clauseTables=['Revision'])545 # Return BranchRevision but prejoin Revision as well.
546 return prejoin(result, slice(0, 1))
543547
544 def canBeDeleted(self):548 def canBeDeleted(self):
545 """See `IBranch`."""549 """See `IBranch`."""
@@ -860,19 +864,17 @@
860864
861 def getScannerData(self):865 def getScannerData(self):
862 """See `IBranch`."""866 """See `IBranch`."""
863 cur = cursor()867 columns = (
864 cur.execute("""868 BranchRevision.id, BranchRevision.sequence, Revision.revision_id)
865 SELECT BranchRevision.id, BranchRevision.sequence,869 rows = Store.of(self).using(Revision, BranchRevision).find(
866 Revision.revision_id870 columns,
867 FROM Revision, BranchRevision871 Revision.id == BranchRevision.revision_id,
868 WHERE Revision.id = BranchRevision.revision872 BranchRevision.branch_id == self.id)
869 AND BranchRevision.branch = %s873 rows = rows.order_by(BranchRevision.sequence)
870 ORDER BY BranchRevision.sequence
871 """ % sqlvalues(self))
872 ancestry = set()874 ancestry = set()
873 history = []875 history = []
874 branch_revision_map = {}876 branch_revision_map = {}
875 for branch_revision_id, sequence, revision_id in cur.fetchall():877 for branch_revision_id, sequence, revision_id in rows:
876 ancestry.add(revision_id)878 ancestry.add(revision_id)
877 branch_revision_map[revision_id] = branch_revision_id879 branch_revision_map[revision_id] = branch_revision_id
878 if sequence is not None:880 if sequence is not None:
@@ -890,19 +892,19 @@
890 prefix = config.launchpad.bzr_imports_root_url892 prefix = config.launchpad.bzr_imports_root_url
891 return urlappend(prefix, '%08x' % self.id)893 return urlappend(prefix, '%08x' % self.id)
892 else:894 else:
893 raise AssertionError("No pull URL for %r" % (self,))895 raise AssertionError("No pull URL for %r" % (self, ))
894896
895 def requestMirror(self):897 def requestMirror(self):
896 """See `IBranch`."""898 """See `IBranch`."""
897 if self.branch_type == BranchType.REMOTE:899 if self.branch_type == BranchType.REMOTE:
898 raise BranchTypeError(self.unique_name)900 raise BranchTypeError(self.unique_name)
899 from canonical.launchpad.interfaces import IStore901 from canonical.launchpad.interfaces import IStore
900 IStore(self).find(902 branch = IStore(self).find(
901 Branch,903 Branch,
902 Branch.id == self.id,904 Branch.id == self.id,
903 Or(Branch.next_mirror_time > UTC_NOW,905 Or(Branch.next_mirror_time > UTC_NOW,
904 Branch.next_mirror_time == None)906 Branch.next_mirror_time == None))
905 ).set(next_mirror_time=UTC_NOW)907 branch.set(next_mirror_time=UTC_NOW)
906 self.next_mirror_time = AutoReload908 self.next_mirror_time = AutoReload
907 return self.next_mirror_time909 return self.next_mirror_time
908910
@@ -997,11 +999,12 @@
997999
998 def commitsForDays(self, since):1000 def commitsForDays(self, since):
999 """See `IBranch`."""1001 """See `IBranch`."""
1002
1000 class DateTrunc(NamedFunc):1003 class DateTrunc(NamedFunc):
1001 name = "date_trunc"1004 name = "date_trunc"
1002 results = Store.of(self).find(1005 results = Store.of(self).find(
1003 (DateTrunc('day', Revision.revision_date), Count(Revision.id)),1006 (DateTrunc('day', Revision.revision_date), Count(Revision.id)),
1004 Revision.id == BranchRevision.revisionID,1007 Revision.id == BranchRevision.revision_id,
1005 Revision.revision_date > since,1008 Revision.revision_date > since,
1006 BranchRevision.branch == self)1009 BranchRevision.branch == self)
1007 results = results.group_by(1010 results = results.group_by(
@@ -1080,6 +1083,7 @@
1080 def __init__(self, affected_object, rationale):1083 def __init__(self, affected_object, rationale):
1081 self.affected_object = ProxyFactory(affected_object)1084 self.affected_object = ProxyFactory(affected_object)
1082 self.rationale = rationale1085 self.rationale = rationale
1086
1083 def __call__(self):1087 def __call__(self):
1084 """Perform the deletion operation."""1088 """Perform the deletion operation."""
1085 raise NotImplementedError(DeletionOperation.__call__)1089 raise NotImplementedError(DeletionOperation.__call__)
@@ -1161,7 +1165,7 @@
11611165
1162 def __init__(self, code_import):1166 def __init__(self, code_import):
1163 DeletionOperation.__init__(1167 DeletionOperation.__init__(
1164 self, code_import, _( 'This is the import data for this branch.'))1168 self, code_import, _('This is the import data for this branch.'))
11651169
1166 def __call__(self):1170 def __call__(self):
1167 from lp.code.model.codeimport import CodeImportSet1171 from lp.code.model.codeimport import CodeImportSet
11681172
=== modified file 'lib/lp/code/model/branchmergeproposal.py'
--- lib/lp/code/model/branchmergeproposal.py 2010-05-07 04:53:47 +0000
+++ lib/lp/code/model/branchmergeproposal.py 2010-07-14 16:19:43 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0611,W0212,F04014# pylint: disable-msg=E0611,W0212,F0401
@@ -13,13 +13,13 @@
13 ]13 ]
1414
15from email.Utils import make_msgid15from email.Utils import make_msgid
16from storm.expr import And, Or, Select16from storm.expr import And, Desc, Join, LeftJoin, Or, Select
17from storm.info import ClassAlias
17from storm.store import Store18from storm.store import Store
18from zope.component import getUtility19from zope.component import getUtility
19from zope.event import notify20from zope.event import notify
20from zope.interface import implements21from zope.interface import implements
2122
22from storm.expr import Join, LeftJoin
23from storm.locals import Int, Reference23from storm.locals import Int, Reference
24from sqlobject import ForeignKey, IntCol, StringCol, SQLMultipleJoin24from sqlobject import ForeignKey, IntCol, StringCol, SQLMultipleJoin
2525
@@ -74,9 +74,17 @@
74 if dupes.count() > 0:74 if dupes.count() > 0:
75 return False75 return False
7676
77 [wip, needs_review, code_approved, rejected,77 [
78 merged, merge_failed, queued, superseded78 wip,
79 ] = BranchMergeProposalStatus.items79 needs_review,
80 code_approved,
81 rejected,
82 merged,
83 merge_failed,
84 queued,
85 superseded,
86 ] = BranchMergeProposalStatus.items
87
80 # Transitioning to code approved, rejected, failed or queued from88 # Transitioning to code approved, rejected, failed or queued from
81 # work in progress, needs review or merge failed needs the89 # work in progress, needs review or merge failed needs the
82 # user to be a valid reviewer, other states are fine.90 # user to be a valid reviewer, other states are fine.
@@ -88,13 +96,13 @@
88 return False96 return False
89 # Non-reviewers can toggle within the reviewed ok states97 # Non-reviewers can toggle within the reviewed ok states
90 # (approved/queued/failed): they can dequeue something they spot an98 # (approved/queued/failed): they can dequeue something they spot an
91 # environmental issue with (queued or failed to approved). Retry things99 # environmental issue with (queued or failed to approved). Retry
92 # that had an environmental issue (failed or approved to queued) and note100 # things that had an environmental issue (failed or approved to
93 # things as failing (approved and queued to failed).101 # queued) and note things as failing (approved and queued to failed).
94 # This is perhaps more generous than needed, but its not clearly wrong102 # This is perhaps more generous than needed, but its not clearly wrong
95 # - a key concern is to prevent non reviewers putting things in the 103 # - a key concern is to prevent non reviewers putting things in the
96 # queue that haven't been oked (and thus moved to approved or one of the104 # queue that haven't been approved (and thus moved to approved or one
97 # workflow states that approved leads to).105 # of the workflow states that approved leads to).
98 elif (next_state in reviewed_ok_states and106 elif (next_state in reviewed_ok_states and
99 from_state not in reviewed_ok_states):107 from_state not in reviewed_ok_states):
100 return False108 return False
@@ -154,14 +162,14 @@
154 from lp.code.model.branchmergeproposaljob import (162 from lp.code.model.branchmergeproposaljob import (
155 BranchMergeProposalJob, BranchMergeProposalJobFactory,163 BranchMergeProposalJob, BranchMergeProposalJobFactory,
156 BranchMergeProposalJobType)164 BranchMergeProposalJobType)
157 job = Store.of(self).find(165 jobs = Store.of(self).find(
158 BranchMergeProposalJob,166 BranchMergeProposalJob,
159 BranchMergeProposalJob.branch_merge_proposal == self,167 BranchMergeProposalJob.branch_merge_proposal == self,
160 BranchMergeProposalJob.job_type ==168 BranchMergeProposalJob.job_type ==
161 BranchMergeProposalJobType.UPDATE_PREVIEW_DIFF,169 BranchMergeProposalJobType.UPDATE_PREVIEW_DIFF,
162 BranchMergeProposalJob.job == Job.id,170 BranchMergeProposalJob.job == Job.id,
163 Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING])171 Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING]))
164 ).order_by(Job.scheduled_start, Job.date_created).first()172 job = jobs.order_by(Job.scheduled_start, Job.date_created).first()
165 if job is not None:173 if job is not None:
166 return BranchMergeProposalJobFactory.create(job)174 return BranchMergeProposalJobFactory.create(job)
167 else:175 else:
@@ -486,8 +494,10 @@
486 self.queue_position = None494 self.queue_position = None
487495
488 if merged_revno is not None:496 if merged_revno is not None:
489 branch_revision = BranchRevision.selectOneBy(497 branch_revision = Store.of(self).find(
490 branch=self.target_branch, sequence=merged_revno)498 BranchRevision,
499 BranchRevision.branch == self.target_branch,
500 BranchRevision.sequence == merged_revno).one()
491 if branch_revision is not None:501 if branch_revision is not None:
492 date_merged = branch_revision.revision.revision_date502 date_merged = branch_revision.revision.revision_date
493503
@@ -579,14 +589,20 @@
579589
580 def getUnlandedSourceBranchRevisions(self):590 def getUnlandedSourceBranchRevisions(self):
581 """See `IBranchMergeProposal`."""591 """See `IBranchMergeProposal`."""
582 return BranchRevision.select('''592 store = Store.of(self)
583 BranchRevision.branch = %s AND593 SourceRevision = ClassAlias(BranchRevision)
584 BranchRevision.sequence IS NOT NULL AND594 TargetRevision = ClassAlias(BranchRevision)
585 BranchRevision.revision NOT IN (595 target_join = LeftJoin(
586 SELECT revision FROM BranchRevision596 TargetRevision, And(
587 WHERE branch = %s)597 TargetRevision.revision_id == SourceRevision.revision_id,
588 ''' % sqlvalues(self.source_branch, self.target_branch),598 TargetRevision.branch_id == self.target_branch.id))
589 prejoins=['revision'], orderBy='-sequence', limit=10)599 origin = [SourceRevision, target_join]
600 result = store.using(*origin).find(
601 SourceRevision,
602 SourceRevision.branch_id == self.source_branch.id,
603 SourceRevision.sequence != None,
604 TargetRevision.id == None)
605 return result.order_by(Desc(SourceRevision.sequence)).config(limit=10)
590606
591 def createComment(self, owner, subject, content=None, vote=None,607 def createComment(self, owner, subject, content=None, vote=None,
592 review_type=None, parent=None, _date_created=DEFAULT,608 review_type=None, parent=None, _date_created=DEFAULT,
@@ -649,9 +665,8 @@
649 CodeReviewVoteReference,665 CodeReviewVoteReference,
650 CodeReviewVoteReference.branch_merge_proposal == self,666 CodeReviewVoteReference.branch_merge_proposal == self,
651 CodeReviewVoteReference.review_type == review_type,667 CodeReviewVoteReference.review_type == review_type,
652 CodeReviewVoteReference.comment == None668 CodeReviewVoteReference.comment == None)
653 ).order_by(CodeReviewVoteReference.date_created)669 for ref in refs.order_by(CodeReviewVoteReference.date_created):
654 for ref in refs:
655 if user.inTeam(ref.reviewer):670 if user.inTeam(ref.reviewer):
656 return ref671 return ref
657 return None672 return None
@@ -849,5 +864,3 @@
849 BranchMergeProposal.target_branch = %s AND864 BranchMergeProposal.target_branch = %s AND
850 BranchMergeProposal.queue_status NOT IN %s865 BranchMergeProposal.queue_status NOT IN %s
851 """ % sqlvalues(source_branch, target_branch, FINAL_STATES))866 """ % sqlvalues(source_branch, target_branch, FINAL_STATES))
852
853
854867
=== modified file 'lib/lp/code/model/branchrevision.py'
--- lib/lp/code/model/branchrevision.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/model/branchrevision.py 2010-07-14 16:19:43 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0611,W02124# pylint: disable-msg=E0611,W0212
@@ -8,30 +8,43 @@
88
9from zope.interface import implements9from zope.interface import implements
1010
11from sqlobject import ForeignKey, IntCol11from storm.locals import (Int, Reference, Storm)
1212
13from canonical.database.sqlbase import SQLBase13from canonical.launchpad.interfaces.lpstorm import IMasterStore
14from lp.code.interfaces.branchrevision import IBranchRevision, IBranchRevisionSet14
15class BranchRevision(SQLBase):15from lp.code.interfaces.branchrevision import (
16 """See IBranchRevision."""16 IBranchRevision, IBranchRevisionSet)
17
18
19class BranchRevision(Storm):
20 """See `IBranchRevision`."""
21 __storm_table__ = 'BranchRevision'
22
23 id = Int(primary=True)
1724
18 implements(IBranchRevision)25 implements(IBranchRevision)
1926
20 _table = 'BranchRevision'27 branch_id = Int(name='branch', allow_none=False)
2128 branch = Reference(branch_id, 'Branch.id')
22 branch = ForeignKey(29
23 dbName='branch', foreignKey='Branch', notNull=True)30 revision_id = Int(name='revision', allow_none=False)
2431 revision = Reference(revision_id, 'Revision.id')
25 sequence = IntCol()32
26 revision = ForeignKey(33 sequence = Int(name='sequence', allow_none=True)
27 dbName='revision', foreignKey='Revision', notNull=True)34
35 def __init__(self, branch, revision, sequence=None):
36 self.branch = branch
37 self.revision = revision
38 self.sequence = sequence
2839
2940
30class BranchRevisionSet:41class BranchRevisionSet:
31 """See IBranchRevisionSet."""42 """See `IBranchRevisionSet`."""
3243
33 implements(IBranchRevisionSet)44 implements(IBranchRevisionSet)
3445
35 def delete(self, branch_revision_id):46 def delete(self, branch_revision_id):
36 """See `IBranchRevisionSet`."""47 """See `IBranchRevisionSet`."""
37 BranchRevision.delete(branch_revision_id)48 match = IMasterStore(BranchRevision).find(
49 BranchRevision, BranchRevision.id == branch_revision_id)
50 match.remove()
3851
=== modified file 'lib/lp/code/model/revision.py'
--- lib/lp/code/model/revision.py 2010-04-16 15:06:55 +0000
+++ lib/lp/code/model/revision.py 2010-07-14 16:19:43 +0000
@@ -111,8 +111,8 @@
111 store = Store.of(self)111 store = Store.of(self)
112112
113 query = And(113 query = And(
114 self.id == BranchRevision.revisionID,114 self.id == BranchRevision.revision_id,
115 BranchRevision.branchID == Branch.id)115 BranchRevision.branch_id == Branch.id)
116 if not allow_private:116 if not allow_private:
117 query = And(query, Not(Branch.private))117 query = And(query, Not(Branch.private))
118 if not allow_junk:118 if not allow_junk:
@@ -123,7 +123,7 @@
123 Or(123 Or(
124 (Branch.product != None),124 (Branch.product != None),
125 And(125 And(
126 Branch.sourcepackagename != None, 126 Branch.sourcepackagename != None,
127 Branch.distroseries != None)))127 Branch.distroseries != None)))
128 result_set = store.find(Branch, query)128 result_set = store.find(Branch, query)
129 if self.revision_author.person is None:129 if self.revision_author.person is None:
@@ -374,7 +374,7 @@
374 BranchRevision.branch == Branch.id,374 BranchRevision.branch == Branch.id,
375 Branch.product == product,375 Branch.product == product,
376 Branch.lifecycle_status.is_in(DEFAULT_BRANCH_STATUS_IN_LISTING),376 Branch.lifecycle_status.is_in(DEFAULT_BRANCH_STATUS_IN_LISTING),
377 BranchRevision.revisionID >= revision_subselect)377 BranchRevision.revision_id >= revision_subselect)
378 result_set.config(distinct=True)378 result_set.config(distinct=True)
379 return result_set.order_by(Desc(Revision.revision_date))379 return result_set.order_by(Desc(Revision.revision_date))
380380
381381
=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
--- lib/lp/code/model/tests/test_branchjob.py 2010-06-11 03:52:02 +0000
+++ lib/lp/code/model/tests/test_branchjob.py 2010-07-14 16:19:43 +0000
@@ -26,6 +26,7 @@
2626
27from canonical.config import config27from canonical.config import config
28from canonical.database.constants import UTC_NOW28from canonical.database.constants import UTC_NOW
29from canonical.launchpad.interfaces.lpstorm import IMasterStore
29from canonical.launchpad.webapp import canonical_url30from canonical.launchpad.webapp import canonical_url
30from canonical.launchpad.webapp.testing import verifyObject31from canonical.launchpad.webapp.testing import verifyObject
31from lp.translations.interfaces.translations import (32from lp.translations.interfaces.translations import (
@@ -113,7 +114,7 @@
113 self.useBzrBranches(direct_database=True)114 self.useBzrBranches(direct_database=True)
114115
115 tree_location = tempfile.mkdtemp()116 tree_location = tempfile.mkdtemp()
116 self.addCleanup(lambda: shutil.rmtree(tree_location)) 117 self.addCleanup(lambda: shutil.rmtree(tree_location))
117118
118 branch, tree = self.create_branch_and_tree(119 branch, tree = self.create_branch_and_tree(
119 tree_location=tree_location)120 tree_location=tree_location)
@@ -331,7 +332,7 @@
331 job = RevisionMailJob.create(332 job = RevisionMailJob.create(
332 branch, 0, 'from@example.com', 'hello', False, 'subject')333 branch, 0, 'from@example.com', 'hello', False, 'subject')
333 job.run()334 job.run()
334 (mail,) = pop_notifications()335 (mail, ) = pop_notifications()
335 self.assertEqual('0', mail['X-Launchpad-Branch-Revision-Number'])336 self.assertEqual('0', mail['X-Launchpad-Branch-Revision-Number'])
336 self.assertEqual('from@example.com', mail['from'])337 self.assertEqual('from@example.com', mail['from'])
337 self.assertEqual('subject', mail['subject'])338 self.assertEqual('subject', mail['subject'])
@@ -344,7 +345,7 @@
344 'To unsubscribe from this branch go to'345 'To unsubscribe from this branch go to'
345 ' %(url)s/+edit-subscription\n' % {346 ' %(url)s/+edit-subscription\n' % {
346 'url': canonical_url(branch),347 'url': canonical_url(branch),
347 'identity': branch.bzr_identity348 'identity': branch.bzr_identity,
348 },349 },
349 mail.get_payload(decode=True))350 mail.get_payload(decode=True))
350351
@@ -454,7 +455,9 @@
454 except bzr_errors.NoSuchRevision:455 except bzr_errors.NoSuchRevision:
455 revno = None456 revno = None
456 if existing is not None:457 if existing is not None:
457 BranchRevision.delete(existing.id)458 branchrevision = IMasterStore(branch).find(
459 BranchRevision, BranchRevision.id == existing.id)
460 branchrevision.remove()
458 branch.createBranchRevision(revno, revision)461 branch.createBranchRevision(revno, revision)
459462
460 def create3CommitsBranch(self):463 def create3CommitsBranch(self):
@@ -913,7 +916,7 @@
913 self.series = None916 self.series = None
914917
915 def _makeBranchWithTreeAndFile(self, file_name, file_content = None):918 def _makeBranchWithTreeAndFile(self, file_name, file_content = None):
916 return self._makeBranchWithTreeAndFiles(((file_name, file_content),))919 return self._makeBranchWithTreeAndFiles(((file_name, file_content), ))
917920
918 def _makeBranchWithTreeAndFiles(self, files):921 def _makeBranchWithTreeAndFiles(self, files):
919 """Create a branch with a tree that contains the given files.922 """Create a branch with a tree that contains the given files.
@@ -984,7 +987,7 @@
984987
985 def _runJobWithFile(self, import_mode, file_name, file_content = None):988 def _runJobWithFile(self, import_mode, file_name, file_content = None):
986 return self._runJobWithFiles(989 return self._runJobWithFiles(
987 import_mode, ((file_name, file_content),))990 import_mode, ((file_name, file_content), ))
988991
989 def _runJobWithFiles(self, import_mode, files,992 def _runJobWithFiles(self, import_mode, files,
990 do_upload_translations=False):993 do_upload_translations=False):
@@ -1018,8 +1021,7 @@
1018 pot_name = "foo.pot"1021 pot_name = "foo.pot"
1019 entries = self._runJobWithFiles(1022 entries = self._runJobWithFiles(
1020 TranslationsBranchImportMode.IMPORT_TEMPLATES,1023 TranslationsBranchImportMode.IMPORT_TEMPLATES,
1021 ((pot_name,), ('eo.po',), ('README',))1024 ((pot_name,), ('eo.po',), ('README',)))
1022 )
1023 self.assertEqual(len(entries), 1)1025 self.assertEqual(len(entries), 1)
1024 entry = entries[0]1026 entry = entries[0]
1025 self.assertEqual(pot_name, entry.path)1027 self.assertEqual(pot_name, entry.path)
@@ -1058,8 +1060,7 @@
1058 pot_name = "en-US.xpi"1060 pot_name = "en-US.xpi"
1059 entries = self._runJobWithFiles(1061 entries = self._runJobWithFiles(
1060 TranslationsBranchImportMode.IMPORT_TEMPLATES,1062 TranslationsBranchImportMode.IMPORT_TEMPLATES,
1061 ((pot_name,), ('eo.xpi',), ('README',))1063 ((pot_name,), ('eo.xpi',), ('README',)))
1062 )
1063 self.assertEqual(len(entries), 1)1064 self.assertEqual(len(entries), 1)
1064 entry = entries[0]1065 entry = entries[0]
1065 self.assertEqual(pot_name, entry.path)1066 self.assertEqual(pot_name, entry.path)
@@ -1108,7 +1109,7 @@
1108 pot_name = "foo.pot"1109 pot_name = "foo.pot"
1109 revision_id = self._makeBranchWithTreeAndFiles(1110 revision_id = self._makeBranchWithTreeAndFiles(
1110 ((pot_name,), ('eo.po',), ('README',)))1111 ((pot_name,), ('eo.po',), ('README',)))
1111 self._commitFilesToTree(((pot_name,),))1112 self._commitFilesToTree(((pot_name, ), ))
1112 entries = self._runJob(1113 entries = self._runJob(
1113 TranslationsBranchImportMode.IMPORT_TEMPLATES, revision_id)1114 TranslationsBranchImportMode.IMPORT_TEMPLATES, revision_id)
1114 self.assertEqual(len(entries), 1)1115 self.assertEqual(len(entries), 1)
@@ -1120,8 +1121,7 @@
1120 # not configured to do so.1121 # not configured to do so.
1121 entries = self._runJobWithFiles(1122 entries = self._runJobWithFiles(
1122 TranslationsBranchImportMode.NO_IMPORT,1123 TranslationsBranchImportMode.NO_IMPORT,
1123 (('foo.pot',), ('eo.po',), ('README',))1124 (('foo.pot',), ('eo.po',), ('README',)))
1124 )
1125 self.assertEqual([], entries)1125 self.assertEqual([], entries)
11261126
1127 def test_upload_translations(self):1127 def test_upload_translations(self):
@@ -1183,7 +1183,7 @@
1183 # only POTemplate object in the database, if there is only one such1183 # only POTemplate object in the database, if there is only one such
1184 # object for this product series.1184 # object for this product series.
1185 self._makeBranchWithTreeAndFiles(1185 self._makeBranchWithTreeAndFiles(
1186 [('foo.pot', None),('bar.pot', None)])1186 [('foo.pot', None), ('bar.pot', None)])
1187 self._makeProductSeries(TranslationsBranchImportMode.IMPORT_TEMPLATES)1187 self._makeProductSeries(TranslationsBranchImportMode.IMPORT_TEMPLATES)
1188 self.factory.makePOTemplate(self.series, path='foo.pot')1188 self.factory.makePOTemplate(self.series, path='foo.pot')
1189 self.factory.makePOTemplate(self.series, path='bar.pot')1189 self.factory.makePOTemplate(self.series, path='bar.pot')
@@ -1354,6 +1354,5 @@
1354 self.assertFalse(os.path.exists(branch_path))1354 self.assertFalse(os.path.exists(branch_path))
13551355
13561356
1357
1358def test_suite():1357def test_suite():
1359 return TestLoader().loadTestsFromName(__name__)1358 return TestLoader().loadTestsFromName(__name__)
13601359
=== modified file 'lib/lp/codehosting/scanner/tests/test_bzrsync.py'
--- lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-06-16 19:47:27 +0000
+++ lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-07-14 16:19:43 +0000
@@ -1,6 +1,6 @@
1#!/usr/bin/python1#!/usr/bin/python
2#2#
3# Copyright 2009 Canonical Ltd. This software is licensed under the3# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).4# GNU Affero General Public License version 3 (see the file LICENSE).
55
6# pylint: disable-msg=W01416# pylint: disable-msg=W0141
@@ -21,6 +21,7 @@
21from zope.security.proxy import removeSecurityProxy21from zope.security.proxy import removeSecurityProxy
2222
23from canonical.config import config23from canonical.config import config
24from canonical.launchpad.interfaces.lpstorm import IStore
24from lp.translations.interfaces.translations import (25from lp.translations.interfaces.translations import (
25 TranslationsBranchImportMode)26 TranslationsBranchImportMode)
26from lp.code.interfaces.branchjob import IRosettaUploadJobSource27from lp.code.interfaces.branchjob import IRosettaUploadJobSource
@@ -36,7 +37,9 @@
36def run_as_db_user(username):37def run_as_db_user(username):
37 """Create a decorator that will run a function as the given database user.38 """Create a decorator that will run a function as the given database user.
38 """39 """
40
39 def _run_with_different_user(f):41 def _run_with_different_user(f):
42
40 def decorated(*args, **kwargs):43 def decorated(*args, **kwargs):
41 current_user = LaunchpadZopelessLayer.txn._dbuser44 current_user = LaunchpadZopelessLayer.txn._dbuser
42 if current_user == username:45 if current_user == username:
@@ -47,6 +50,7 @@
47 finally:50 finally:
48 LaunchpadZopelessLayer.switchDbUser(current_user)51 LaunchpadZopelessLayer.switchDbUser(current_user)
49 return mergeFunctionMetadata(f, decorated)52 return mergeFunctionMetadata(f, decorated)
53
50 return _run_with_different_user54 return _run_with_different_user
5155
5256
@@ -94,10 +98,12 @@
94 :return: (num_revisions, num_branch_revisions, num_revision_parents,98 :return: (num_revisions, num_branch_revisions, num_revision_parents,
95 num_revision_authors)99 num_revision_authors)
96 """100 """
97 return (Revision.select().count(),101 store = IStore(Revision)
98 BranchRevision.select().count(),102 return (
99 RevisionParent.select().count(),103 store.find(Revision).count(),
100 RevisionAuthor.select().count())104 store.find(BranchRevision).count(),
105 store.find(RevisionParent).count(),
106 store.find(RevisionAuthor).count())
101107
102 def assertCounts(self, counts, new_revisions=0, new_numbers=0,108 def assertCounts(self, counts, new_revisions=0, new_numbers=0,
103 new_parents=0, new_authors=0):109 new_parents=0, new_authors=0):
@@ -228,10 +234,10 @@
228 :return: A set of tuples (sequence, revision-id) for all the234 :return: A set of tuples (sequence, revision-id) for all the
229 BranchRevisions rows belonging to self.db_branch.235 BranchRevisions rows belonging to self.db_branch.
230 """236 """
231 return set(237 return set(IStore(BranchRevision).find(
232 (branch_revision.sequence, branch_revision.revision.revision_id)238 (BranchRevision.sequence, Revision.revision_id),
233 for branch_revision239 Revision.id == BranchRevision.revision_id,
234 in BranchRevision.selectBy(branch=db_branch))240 BranchRevision.branch == db_branch))
235241
236 def writeToFile(self, filename="file", contents=None):242 def writeToFile(self, filename="file", contents=None):
237 """Set the contents of the specified file.243 """Set the contents of the specified file.
@@ -459,8 +465,9 @@
459 # retrieveDatabaseAncestry.465 # retrieveDatabaseAncestry.
460 branch = getUtility(IBranchLookup).getByUniqueName(466 branch = getUtility(IBranchLookup).getByUniqueName(
461 '~name12/+junk/junk.contrib')467 '~name12/+junk/junk.contrib')
462 sampledata = list(468 branch_revisions = IStore(BranchRevision).find(
463 BranchRevision.selectBy(branch=branch).orderBy('sequence'))469 BranchRevision, BranchRevision.branch == branch)
470 sampledata = list(branch_revisions.order_by(BranchRevision.sequence))
464 expected_ancestry = set(branch_revision.revision.revision_id471 expected_ancestry = set(branch_revision.revision.revision_id
465 for branch_revision in sampledata)472 for branch_revision in sampledata)
466 expected_history = [branch_revision.revision.revision_id473 expected_history = [branch_revision.revision.revision_id
@@ -540,7 +547,7 @@
540547
541 def test_upload_on_new_revision_series_not_configured(self):548 def test_upload_on_new_revision_series_not_configured(self):
542 # Syncing a branch with a changed tip does not create a549 # Syncing a branch with a changed tip does not create a
543 # new RosettaUploadJob if the linked product series is not 550 # new RosettaUploadJob if the linked product series is not
544 # configured for translation uploads.551 # configured for translation uploads.
545 self._makeProductSeries()552 self._makeProductSeries()
546 self.commitRevision()553 self.commitRevision()
547554
=== modified file 'lib/lp/services/database/prejoin.py'
--- lib/lp/services/database/prejoin.py 2009-10-28 12:11:35 +0000
+++ lib/lp/services/database/prejoin.py 2010-07-14 16:19:43 +0000
@@ -10,6 +10,7 @@
1010
11from lazr.delegates import delegates11from lazr.delegates import delegates
1212
13
13class PrejoinResultSet:14class PrejoinResultSet:
14 """Prejoin support.15 """Prejoin support.
1516
@@ -21,12 +22,12 @@
21 The preferred solution is support in Storm core, so we can just do22 The preferred solution is support in Storm core, so we can just do
22 something like:23 something like:
2324
24 >>> results = store.find(Product).prejoin(25 # Select Product, but prejoin the owner as well.
25 ... (Person, EmailAddress),26 >>> join = store.find((Product, Person), Product.name == name)
26 ... Product._ownerID == Person.id,27 >>> results = prejoin(join, slice(0, 1))
27 ... EmailAddress.personID == Person.id)
28 """28 """
29 delegates(IResultSet, context='result_set')29 delegates(IResultSet, context='result_set')
30
30 def __init__(self, result_set, return_slice=slice(0, 1)):31 def __init__(self, result_set, return_slice=slice(0, 1)):
31 self.result_set = result_set32 self.result_set = result_set
32 self.return_slice = return_slice33 self.return_slice = return_slice

Subscribers

People subscribed via source and target branches

to status/vote changes: