Merge lp:~thumper/launchpad/package-branch-listing-speedup into lp:launchpad
- package-branch-listing-speedup
- Merge into devel
| Status: | Merged |
|---|---|
| Approved by: | Michael Hudson-Doyle on 2010-04-08 |
| Approved revision: | no longer in the source branch. |
| Merged at revision: | not available |
| Proposed branch: | lp:~thumper/launchpad/package-branch-listing-speedup |
| Merge into: | lp:launchpad |
| Diff against target: |
1396 lines (+612/-315) 17 files modified
database/schema/security.cfg (+1/-0) lib/lp/archiveuploader/tests/test_recipeuploads.py (+1/-1) lib/lp/code/browser/branchlisting.py (+5/-36) lib/lp/code/browser/tests/test_branchlisting.py (+1/-0) lib/lp/code/configure.zcml (+2/-0) lib/lp/code/interfaces/branch.py (+76/-47) lib/lp/code/interfaces/branchtarget.py (+3/-0) lib/lp/code/interfaces/linkedbranch.py (+1/-0) lib/lp/code/model/branch.py (+3/-19) lib/lp/code/model/branchtarget.py (+15/-0) lib/lp/code/model/linkedbranch.py (+124/-25) lib/lp/code/model/tests/test_branch.py (+197/-0) lib/lp/code/model/tests/test_branchtarget.py (+12/-0) lib/lp/code/model/tests/test_linkedbranch.py (+144/-0) lib/lp/code/stories/branches/xx-branch-listings.txt (+1/-4) lib/lp/code/templates/branch-listing.pt (+4/-172) lib/lp/testing/factory.py (+22/-11) |
| To merge this branch: | bzr merge lp:~thumper/launchpad/package-branch-listing-speedup |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Michael Hudson-Doyle | 2010-04-08 | Approve on 2010-04-08 | |
|
Review via email:
|
|||
Commit Message
Refactor the bzr_identity calculation.
Description of the Change
This branch changes how bzr identities are worked out.
Instead of a chunk of logic sitting in a stand alone function, the algorithm
now says: work out the links a branch has, sort them, and pick the first.
tests:
test_
TestBranchLin
TestBzrIdentity
TestLinkedBra
qa:
look at https:/
and make sure it looks right
changes:
pre and mid impl calls with mwhudson
lib/lp/
* this is mostly simplification
* the BranchListingItem now inherits from BzrIdentityMixin, this is to have
the 'self' variable pointing at the BranchListingItem rather than the
delegated branch object. This allows the associatedProdu
associatedSu
* the is_dev_focus is no longer needed, so the methods to work it out are
removed
lib/lp/
* adds a single test to make sure there are no extra queries executed
to work out the bzr_identity of a BranchListingItem
lib/lp/
* Makes the is_personal_branch, branchIdentities, and branchLinks
methods available through IBranch
lib/lp/
* Removes the bazaar_identity
* Adds the BzrIdentityMixin
lib/lp/
* Makes the context accessible from the object.
lib/lp/
* Implements the is_personal_branch and adds in the bzr identity mixin class.
* The bzr_identity function is removed as it is now provided by the mixin.
lib/lp/
* Link objects are now sortable, so the most 'important' links appear first.
lib/lp/
lib/lp/
* implement the tests mentioned above
lib/lp/
* There were many places where a string was being passed in for a
sourcepackag
sourcepackag
ability to pass in a string for the sourcepackagename, and the
correct object will be created.
| Tim Penhey (thumper) wrote : | # |
> Just a few comments below, mostly simple clarity stuff. Thanks for
> fixing the icky bug!
Yeah, it'll be good to get this cleaned up.
> > + def branchIdentites():
> Missing an 'i' here :)
Fixed.
> > + """A list of aliases for a branch.
> > +
> > + Returns a list of tuples of bzr identity and context object.
> > There is + at least one alias for any branch, and that is the
> > branch itself. For + linked branches, the context object is the
> > appropriate linked object. + """
>
> I'm afraid these two docstrings both need work. The first doesn't
> explain which ICanHasLinkedBranch objects are returned, nor what being
> 'sorted' means in this context.
>
> The second I think needs to explain itself a bit more as well. It
> seems to blur the concept of 'alias' (a concept we don't have anywhere
> else) and the linked object that the alias is derived from. Can you
> just try to expand it a bit, maybe with an example or two?
>
> In this branch it's not really obvious why branchLinks and
> branchIdentities have a separate existence to the bzr_identity
> property. I guess this is preparation for that portlet you want to
> add? I hope if the implementation of that doesn't require all these
> variants we can shrink them down a bit.
Updated.
> > === modified file 'lib/lp/
> > + # When a project gets the series they are ordered
> > alphabetically + # by name.
>
> I don't think this sentence makes sense.
>
> I don't suppose it really matters, but sorting series by name isn't
> how it's usually done in Launchpad; sorting trunk first and then by
> comparing date_created is more common I think?
I looked at the product code to see how it returned them. Generally it just
sorted alphabetically, which I think is OK.
Comment updated too.
> > === modified file 'lib/lp/
> > +class TestBranchLinks
> > + """Test IBranch.branchLinks and IBranch.
> > +
> > + layer = DatabaseFunctio
> > +
> > + def test_default_
> > + # If there are no links, the branch identities is just the
> > unique + # name.
>
> This sentence isn't very grammatical, maybe "If there are no links,
> the only branch identity is the unique name"?
Updated.
> > + def test_linked_
> > + # If a branch is linked to a non-development series of a product
> > and + # not linked to the product itself, then the product is not
> > returned + # in the links.
>
> I think the more positive "only the product series is returned in the
> links" is easier to understand than "the product is not returned".
Updated too.
> > + def test_linked_
> > + # If a branch is the development focus branch for a product,
> > then it's + # bzr identity is lp:product.
>
> This comment looks entirely wrong :-)
And this.
> > class TestBzrIdentity
> > """Test IBranch.
>
> You don't test here any branches that are linked to both products and
> sou...
| 1 | === modified file 'lib/lp/code/configure.zcml' |
| 2 | --- lib/lp/code/configure.zcml 2010-04-08 02:08:15 +0000 |
| 3 | +++ lib/lp/code/configure.zcml 2010-04-09 01:30:22 +0000 |
| 4 | @@ -402,7 +402,6 @@ |
| 5 | composePublicURL |
| 6 | whiteboard |
| 7 | target |
| 8 | - is_personal_branch |
| 9 | mirror_status_message |
| 10 | private |
| 11 | registrant |
| 12 | |
| 13 | === modified file 'lib/lp/code/interfaces/branch.py' |
| 14 | --- lib/lp/code/interfaces/branch.py 2010-04-08 02:56:31 +0000 |
| 15 | +++ lib/lp/code/interfaces/branch.py 2010-04-09 01:46:52 +0000 |
| 16 | @@ -496,14 +496,6 @@ |
| 17 | "None if not a package branch."), |
| 18 | schema=Interface, required=False, readonly=True)) |
| 19 | |
| 20 | - is_personal_branch = exported( |
| 21 | - Bool( |
| 22 | - title=_("Personal Branch"), required=False, |
| 23 | - readonly=True, default=False, |
| 24 | - description=_( |
| 25 | - "A branch is a personal branch if it is not associated " |
| 26 | - "with a project nor a source package."))) |
| 27 | - |
| 28 | code_reviewer = Attribute( |
| 29 | "The reviewer if set, otherwise the owner of the branch.") |
| 30 | |
| 31 | @@ -932,14 +924,34 @@ |
| 32 | """Return the suite source packages that this branch is linked to.""" |
| 33 | |
| 34 | def branchLinks(): |
| 35 | - """Return a sorted list of ICanHasLinkedBranch objects.""" |
| 36 | - |
| 37 | - def branchIdentites(): |
| 38 | + """Return a sorted list of ICanHasLinkedBranch objects. |
| 39 | + |
| 40 | + There is one result for each related linked object that the branch is |
| 41 | + linked to. For example in the case where a branch is linked to the |
| 42 | + development series of a project, the link objects for both the project |
| 43 | + and the development series are returned. |
| 44 | + |
| 45 | + The sorting uses the defined order of the linked objects where the |
| 46 | + more important links are sorted first. |
| 47 | + """ |
| 48 | + |
| 49 | + def branchIdentities(): |
| 50 | """A list of aliases for a branch. |
| 51 | |
| 52 | Returns a list of tuples of bzr identity and context object. There is |
| 53 | at least one alias for any branch, and that is the branch itself. For |
| 54 | linked branches, the context object is the appropriate linked object. |
| 55 | + |
| 56 | + Where a branch is linked to a product series or a suite source |
| 57 | + package, the branch is available through a number of different urls. |
| 58 | + These urls are the aliases for the branch. |
| 59 | + |
| 60 | + For example, a branch linked to the development focus of the 'fooix' |
| 61 | + project is accessible using: |
| 62 | + lp:fooix - the linked object is the product fooix |
| 63 | + lp:fooix/trunk - the linked object is the trunk series of fooix |
| 64 | + lp:~owner/fooix/name - the unique name of the branch where the linked |
| 65 | + object is the branch itself. |
| 66 | """ |
| 67 | |
| 68 | # subscription-related methods |
| 69 | @@ -1337,7 +1349,7 @@ |
| 70 | def branchIdentities(self): |
| 71 | """See `IBranch`.""" |
| 72 | lp_prefix = config.codehosting.bzr_lp_prefix |
| 73 | - if self.private or self.is_personal_branch: |
| 74 | + if self.private or not self.target.supports_short_identites: |
| 75 | # XXX: thumper 2010-04-08, bug 261609 |
| 76 | # We have to get around to fixing this |
| 77 | identities = [] |
| 78 | |
| 79 | === modified file 'lib/lp/code/interfaces/branchtarget.py' |
| 80 | --- lib/lp/code/interfaces/branchtarget.py 2010-01-05 08:18:23 +0000 |
| 81 | +++ lib/lp/code/interfaces/branchtarget.py 2010-04-09 01:30:22 +0000 |
| 82 | @@ -88,6 +88,9 @@ |
| 83 | supports_merge_proposals = Attribute( |
| 84 | "Does this target support merge proposals at all?") |
| 85 | |
| 86 | + supports_short_identites = Attribute( |
| 87 | + "Does this target support shortened bazaar identities?") |
| 88 | + |
| 89 | def areBranchesMergeable(other_target): |
| 90 | """Are branches from other_target mergeable into this target.""" |
| 91 | |
| 92 | |
| 93 | === modified file 'lib/lp/code/model/branch.py' |
| 94 | --- lib/lp/code/model/branch.py 2010-04-08 02:08:45 +0000 |
| 95 | +++ lib/lp/code/model/branch.py 2010-04-09 01:30:22 +0000 |
| 96 | @@ -178,10 +178,6 @@ |
| 97 | target = self.product |
| 98 | return IBranchTarget(target) |
| 99 | |
| 100 | - @property |
| 101 | - def is_personal_branch(self): |
| 102 | - return self.product is None and self.distroseries is None |
| 103 | - |
| 104 | def setTarget(self, user, project=None, source_package=None): |
| 105 | """See `IBranch`.""" |
| 106 | if project is not None: |
| 107 | |
| 108 | === modified file 'lib/lp/code/model/branchtarget.py' |
| 109 | --- lib/lp/code/model/branchtarget.py 2010-01-05 08:18:23 +0000 |
| 110 | +++ lib/lp/code/model/branchtarget.py 2010-04-09 01:30:22 +0000 |
| 111 | @@ -95,6 +95,11 @@ |
| 112 | """See `IBranchTarget`.""" |
| 113 | return True |
| 114 | |
| 115 | + @property |
| 116 | + def supports_short_identites(self): |
| 117 | + """See `IBranchTarget`.""" |
| 118 | + return True |
| 119 | + |
| 120 | def areBranchesMergeable(self, other_target): |
| 121 | """See `IBranchTarget`.""" |
| 122 | # Branches are mergable into a PackageTarget if the source package |
| 123 | @@ -182,6 +187,11 @@ |
| 124 | """See `IBranchTarget`.""" |
| 125 | return False |
| 126 | |
| 127 | + @property |
| 128 | + def supports_short_identites(self): |
| 129 | + """See `IBranchTarget`.""" |
| 130 | + return False |
| 131 | + |
| 132 | def areBranchesMergeable(self, other_target): |
| 133 | """See `IBranchTarget`.""" |
| 134 | return False |
| 135 | @@ -258,6 +268,11 @@ |
| 136 | """See `IBranchTarget`.""" |
| 137 | return True |
| 138 | |
| 139 | + @property |
| 140 | + def supports_short_identites(self): |
| 141 | + """See `IBranchTarget`.""" |
| 142 | + return True |
| 143 | + |
| 144 | def areBranchesMergeable(self, other_target): |
| 145 | """See `IBranchTarget`.""" |
| 146 | # Branches are mergable into a PackageTarget if the source package |
| 147 | |
| 148 | === modified file 'lib/lp/code/model/linkedbranch.py' |
| 149 | --- lib/lp/code/model/linkedbranch.py 2010-04-08 00:40:21 +0000 |
| 150 | +++ lib/lp/code/model/linkedbranch.py 2010-04-09 01:46:52 +0000 |
| 151 | @@ -62,8 +62,8 @@ |
| 152 | if result != 0: |
| 153 | return result |
| 154 | else: |
| 155 | - # When a project gets the series they are ordered alphabetically |
| 156 | - # by name. |
| 157 | + # The sorting of the product series uses the same sorting the |
| 158 | + # product itself uses, which is alphabetically by name. |
| 159 | my_parts = ( |
| 160 | self.product_series.product.name, |
| 161 | self.product_series.name) |
| 162 | |
| 163 | === modified file 'lib/lp/code/model/tests/test_branch.py' |
| 164 | --- lib/lp/code/model/tests/test_branch.py 2010-04-08 02:07:15 +0000 |
| 165 | +++ lib/lp/code/model/tests/test_branch.py 2010-04-09 01:46:52 +0000 |
| 166 | @@ -489,8 +489,7 @@ |
| 167 | layer = DatabaseFunctionalLayer |
| 168 | |
| 169 | def test_default_identities(self): |
| 170 | - # If there are no links, the branch identities is just the unique |
| 171 | - # name. |
| 172 | + # If there are no links, the only branch identity is the unique name. |
| 173 | branch = self.factory.makeAnyBranch() |
| 174 | self.assertEqual( |
| 175 | [('lp://dev/' + branch.unique_name, branch)], |
| 176 | @@ -517,8 +516,8 @@ |
| 177 | |
| 178 | def test_linked_to_product_series(self): |
| 179 | # If a branch is linked to a non-development series of a product and |
| 180 | - # not linked to the product itself, then the product is not returned |
| 181 | - # in the links. |
| 182 | + # not linked to the product itself, then only the product series is |
| 183 | + # returned in the links. |
| 184 | fooix = removeSecurityProxy(self.factory.makeProduct(name='fooix')) |
| 185 | future = self.factory.makeProductSeries(product=fooix, name='future') |
| 186 | eric = self.factory.makePerson(name='eric') |
| 187 | |
| 188 | === modified file 'lib/lp/code/model/tests/test_branchtarget.py' |
| 189 | --- lib/lp/code/model/tests/test_branchtarget.py 2010-03-18 15:39:58 +0000 |
| 190 | +++ lib/lp/code/model/tests/test_branchtarget.py 2010-04-09 01:30:22 +0000 |
| 191 | @@ -121,6 +121,10 @@ |
| 192 | # Package branches do support merge proposals. |
| 193 | self.assertTrue(self.target.supports_merge_proposals) |
| 194 | |
| 195 | + def test_supports_short_identites(self): |
| 196 | + # Package branches do support short bzr identites. |
| 197 | + self.assertTrue(self.target.supports_short_identites) |
| 198 | + |
| 199 | def test_displayname(self): |
| 200 | # The display name of a source package target is the display name of |
| 201 | # the source package. |
| 202 | @@ -217,6 +221,10 @@ |
| 203 | # Personal branches do not support merge proposals. |
| 204 | self.assertFalse(self.target.supports_merge_proposals) |
| 205 | |
| 206 | + def test_supports_short_identites(self): |
| 207 | + # Personal branches do not support short bzr identites. |
| 208 | + self.assertFalse(self.target.supports_short_identites) |
| 209 | + |
| 210 | def test_displayname(self): |
| 211 | # The display name of a person branch target is ~$USER/+junk. |
| 212 | target = IBranchTarget(self.original) |
| 213 | @@ -320,6 +328,10 @@ |
| 214 | # Product branches do support merge proposals. |
| 215 | self.assertTrue(self.target.supports_merge_proposals) |
| 216 | |
| 217 | + def test_supports_short_identites(self): |
| 218 | + # Product branches do support short bzr identites. |
| 219 | + self.assertTrue(self.target.supports_short_identites) |
| 220 | + |
| 221 | def test_displayname(self): |
| 222 | # The display name of a product branch target is the display name of |
| 223 | # the product. |
| 224 | |
| 225 | === modified file 'lib/lp/code/model/tests/test_linkedbranch.py' |
| 226 | --- lib/lp/code/model/tests/test_linkedbranch.py 2010-04-07 02:51:37 +0000 |
| 227 | +++ lib/lp/code/model/tests/test_linkedbranch.py 2010-04-09 01:46:52 +0000 |
| 228 | @@ -229,8 +229,8 @@ |
| 229 | |
| 230 | def test_product_sort(self): |
| 231 | # If in the extremely unlikely event we have one branch linked as the |
| 232 | - # trunk of two different products (you never know), then the sorting |
| 233 | - # reverts to the name of the product. |
| 234 | + # trunk of two or more different products (you never know), then the |
| 235 | + # sorting reverts to the name of the product. |
| 236 | aardvark_link = ICanHasLinkedBranch( |
| 237 | self.factory.makeProduct(name='aardvark')) |
| 238 | meerkat_link = ICanHasLinkedBranch( |
| 239 | |
| 240 | === modified file 'lib/lp/code/stories/webservice/xx-branch.txt' |
| 241 | --- lib/lp/code/stories/webservice/xx-branch.txt 2010-04-08 02:19:13 +0000 |
| 242 | +++ lib/lp/code/stories/webservice/xx-branch.txt 2010-04-09 01:30:22 +0000 |
| 243 | @@ -115,7 +115,6 @@ |
| 244 | dependent_branches_collection_link: u'.../~eric/fooix/trunk/dependent_branches' |
| 245 | description: None |
| 246 | display_name: u'lp://dev/~eric/fooix/trunk' |
| 247 | - is_personal_branch: False |
| 248 | landing_candidates_collection_link: u'.../~eric/fooix/trunk/landing_candidates' |
| 249 | landing_targets_collection_link: u'.../~eric/fooix/trunk/landing_targets' |
| 250 | last_mirror_attempt: None |
Preview Diff
| 1 | === modified file 'database/schema/security.cfg' |
| 2 | --- database/schema/security.cfg 2010-04-08 08:55:10 +0000 |
| 3 | +++ database/schema/security.cfg 2010-04-12 05:33:38 +0000 |
| 4 | @@ -1677,6 +1677,7 @@ |
| 5 | public.product = SELECT |
| 6 | public.productseries = SELECT |
| 7 | public.project = SELECT |
| 8 | +public.seriessourcepackagebranch = SELECT |
| 9 | public.sourcepackagename = SELECT |
| 10 | public.staticdiff = SELECT, INSERT |
| 11 | public.teamparticipation = SELECT |
| 12 | |
| 13 | === modified file 'lib/lp/archiveuploader/tests/test_recipeuploads.py' |
| 14 | --- lib/lp/archiveuploader/tests/test_recipeuploads.py 2010-03-06 04:57:40 +0000 |
| 15 | +++ lib/lp/archiveuploader/tests/test_recipeuploads.py 2010-04-12 05:33:38 +0000 |
| 16 | @@ -33,7 +33,7 @@ |
| 17 | self.recipe = self.factory.makeSourcePackageRecipe() |
| 18 | self.build = getUtility(ISourcePackageRecipeBuildSource).new( |
| 19 | sourcepackage=self.factory.makeSourcePackage( |
| 20 | - sourcename='bar', distroseries=self.breezy), |
| 21 | + sourcepackagename='bar', distroseries=self.breezy), |
| 22 | recipe=self.recipe, archive=self.factory.makeArchive( |
| 23 | distribution=self.ubuntu, owner=self.recipe.owner), |
| 24 | requester=self.recipe.owner) |
| 25 | |
| 26 | === modified file 'lib/lp/code/browser/branchlisting.py' |
| 27 | --- lib/lp/code/browser/branchlisting.py 2010-02-16 20:36:48 +0000 |
| 28 | +++ lib/lp/code/browser/branchlisting.py 2010-04-12 05:33:38 +0000 |
| 29 | @@ -74,7 +74,7 @@ |
| 30 | from lp.code.enums import ( |
| 31 | BranchLifecycleStatus, BranchLifecycleStatusFilter, BranchType) |
| 32 | from lp.code.interfaces.branch import ( |
| 33 | - bazaar_identity, DEFAULT_BRANCH_STATUS_IN_LISTING, IBranch, |
| 34 | + BzrIdentityMixin, DEFAULT_BRANCH_STATUS_IN_LISTING, IBranch, |
| 35 | IBranchBatchNavigator, IBranchListingQueryOptimiser) |
| 36 | from lp.code.interfaces.branchcollection import IAllBranches |
| 37 | from lp.code.interfaces.branchnamespace import IBranchNamespacePolicy |
| 38 | @@ -137,7 +137,7 @@ |
| 39 | return HasBadgeBase.getBadge(self, badge_name) |
| 40 | |
| 41 | |
| 42 | -class BranchListingItem(BranchBadges): |
| 43 | +class BranchListingItem(BzrIdentityMixin, BranchBadges): |
| 44 | """A decorated branch. |
| 45 | |
| 46 | Some attributes that we want to display are too convoluted or expensive |
| 47 | @@ -148,7 +148,7 @@ |
| 48 | |
| 49 | def __init__(self, branch, last_commit, now, show_bug_badge, |
| 50 | show_blueprint_badge, show_mp_badge, |
| 51 | - associated_product_series, suite_source_packages, is_dev_focus): |
| 52 | + associated_product_series, suite_source_packages): |
| 53 | BranchBadges.__init__(self, branch) |
| 54 | self.last_commit = last_commit |
| 55 | self.show_bug_badge = show_bug_badge |
| 56 | @@ -157,7 +157,6 @@ |
| 57 | self._now = now |
| 58 | self.associated_product_series = associated_product_series |
| 59 | self.suite_source_packages = suite_source_packages |
| 60 | - self.is_development_focus = is_dev_focus |
| 61 | |
| 62 | def associatedProductSeries(self): |
| 63 | """Override the IBranch.associatedProductSeries.""" |
| 64 | @@ -173,11 +172,6 @@ |
| 65 | if series.status != SeriesStatus.OBSOLETE] |
| 66 | |
| 67 | @property |
| 68 | - def bzr_identity(self): |
| 69 | - """Produce the bzr identity from our known associated series.""" |
| 70 | - return bazaar_identity(self, self.is_development_focus) |
| 71 | - |
| 72 | - @property |
| 73 | def since_updated(self): |
| 74 | """How long since the branch was last updated.""" |
| 75 | return self._now - self.context.date_last_modified |
| 76 | @@ -397,30 +391,6 @@ |
| 77 | self._distro_series_map[distribution] = result |
| 78 | return result |
| 79 | |
| 80 | - def isBranchDevFocus(self, branch, |
| 81 | - associated_product_series, suite_source_packages): |
| 82 | - """Is the branch the development focus? |
| 83 | - |
| 84 | - For product branches this means that the branch is linked to the |
| 85 | - development focus series. |
| 86 | - |
| 87 | - For package branches this means that the branch is linked to the |
| 88 | - release pocket of the development series. |
| 89 | - """ |
| 90 | - # Refactor this code to work for model.branch too? |
| 91 | - # Do we care if a non-product branch is linked to the product series? |
| 92 | - # Do we care if a non-package branch is linked to the package? |
| 93 | - # A) not right now. |
| 94 | - for series in associated_product_series: |
| 95 | - if series.product.development_focus == series: |
| 96 | - return True |
| 97 | - for ssp in suite_source_packages: |
| 98 | - if (ssp.pocket == PackagePublishingPocket.RELEASE and |
| 99 | - ssp.distroseries == self.getDistroDevelSeries( |
| 100 | - ssp.distribution)): |
| 101 | - return True |
| 102 | - return False |
| 103 | - |
| 104 | @cachedproperty |
| 105 | def branch_ids_with_bug_links(self): |
| 106 | """Return a set of branch ids that should show bug badges.""" |
| 107 | @@ -470,12 +440,10 @@ |
| 108 | show_mp_badge = branch.id in self.branch_ids_with_merge_proposals |
| 109 | associated_product_series = self.getProductSeries(branch) |
| 110 | suite_source_packages = self.getSuiteSourcePackages(branch) |
| 111 | - is_dev_focus = self.isBranchDevFocus( |
| 112 | - branch, associated_product_series, suite_source_packages) |
| 113 | return BranchListingItem( |
| 114 | branch, last_commit, self._now, show_bug_badge, |
| 115 | show_blueprint_badge, show_mp_badge, |
| 116 | - associated_product_series, suite_source_packages, is_dev_focus) |
| 117 | + associated_product_series, suite_source_packages) |
| 118 | |
| 119 | def decoratedBranches(self, branches): |
| 120 | """Return the decorated branches for the branches passed in.""" |
| 121 | @@ -1351,6 +1319,7 @@ |
| 122 | no_sort_by = (BranchListingSort.DEFAULT,) |
| 123 | extra_columns = ('author', 'product') |
| 124 | label_template = 'Bazaar branches of %(displayname)s' |
| 125 | + show_series_links = True |
| 126 | |
| 127 | def _getCollection(self): |
| 128 | return getUtility(IAllBranches).inProject(self.context) |
| 129 | |
| 130 | === modified file 'lib/lp/code/browser/tests/test_branchlisting.py' |
| 131 | --- lib/lp/code/browser/tests/test_branchlisting.py 2010-03-30 09:56:10 +0000 |
| 132 | +++ lib/lp/code/browser/tests/test_branchlisting.py 2010-04-12 05:33:38 +0000 |
| 133 | @@ -335,6 +335,7 @@ |
| 134 | # There is only one branch. |
| 135 | batch = view.branches() |
| 136 | [view_branch] = batch.branches |
| 137 | + self.assertStatementCount(0, getattr, view_branch, 'bzr_identity') |
| 138 | self.assertEqual(identity, view_branch.bzr_identity) |
| 139 | |
| 140 | |
| 141 | |
| 142 | === modified file 'lib/lp/code/configure.zcml' |
| 143 | --- lib/lp/code/configure.zcml 2010-04-06 20:17:04 +0000 |
| 144 | +++ lib/lp/code/configure.zcml 2010-04-12 05:33:38 +0000 |
| 145 | @@ -456,6 +456,8 @@ |
| 146 | associatedProductSeries |
| 147 | getProductSeriesPushingTranslations |
| 148 | associatedSuiteSourcePackages |
| 149 | + branchIdentities |
| 150 | + branchLinks |
| 151 | subscribe |
| 152 | getSubscription |
| 153 | hasSubscription |
| 154 | |
| 155 | === modified file 'lib/lp/code/interfaces/branch.py' |
| 156 | --- lib/lp/code/interfaces/branch.py 2010-04-06 17:19:23 +0000 |
| 157 | +++ lib/lp/code/interfaces/branch.py 2010-04-12 05:33:38 +0000 |
| 158 | @@ -8,7 +8,6 @@ |
| 159 | __metaclass__ = type |
| 160 | |
| 161 | __all__ = [ |
| 162 | - 'bazaar_identity', |
| 163 | 'BRANCH_NAME_VALIDATION_ERROR_MESSAGE', |
| 164 | 'branch_name_validator', |
| 165 | 'BranchCannotBePrivate', |
| 166 | @@ -21,6 +20,7 @@ |
| 167 | 'BranchExists', |
| 168 | 'BranchTargetError', |
| 169 | 'BranchTypeError', |
| 170 | + 'BzrIdentityMixin', |
| 171 | 'CannotDeleteBranch', |
| 172 | 'DEFAULT_BRANCH_STATUS_IN_LISTING', |
| 173 | 'get_blacklisted_hostnames', |
| 174 | @@ -32,12 +32,10 @@ |
| 175 | 'IBranchNavigationMenu', |
| 176 | 'IBranchSet', |
| 177 | 'NoSuchBranch', |
| 178 | - 'UnknownBranchTypeError', |
| 179 | 'user_has_special_branch_access', |
| 180 | ] |
| 181 | |
| 182 | from cgi import escape |
| 183 | -from operator import attrgetter |
| 184 | import re |
| 185 | |
| 186 | from zope.component import getUtility |
| 187 | @@ -69,12 +67,14 @@ |
| 188 | ) |
| 189 | from lp.code.interfaces.branchlookup import IBranchLookup |
| 190 | from lp.code.interfaces.branchtarget import IHasBranchTarget |
| 191 | +from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch |
| 192 | from lp.code.interfaces.hasbranches import IHasMergeProposals |
| 193 | from lp.code.interfaces.hasrecipes import IHasRecipes |
| 194 | from canonical.launchpad.interfaces.launchpad import ( |
| 195 | ILaunchpadCelebrities, IPrivacy) |
| 196 | from lp.registry.interfaces.role import IHasOwner |
| 197 | from lp.registry.interfaces.person import IPerson |
| 198 | +from lp.registry.interfaces.pocket import PackagePublishingPocket |
| 199 | from canonical.launchpad.webapp.interfaces import ( |
| 200 | ITableBatchNavigator, NameLookupFailed) |
| 201 | from canonical.launchpad.webapp.menu import structured |
| 202 | @@ -925,6 +925,37 @@ |
| 203 | def associatedSuiteSourcePackages(): |
| 204 | """Return the suite source packages that this branch is linked to.""" |
| 205 | |
| 206 | + def branchLinks(): |
| 207 | + """Return a sorted list of ICanHasLinkedBranch objects. |
| 208 | + |
| 209 | + There is one result for each related linked object that the branch is |
| 210 | + linked to. For example in the case where a branch is linked to the |
| 211 | + development series of a project, the link objects for both the project |
| 212 | + and the development series are returned. |
| 213 | + |
| 214 | + The sorting uses the defined order of the linked objects where the |
| 215 | + more important links are sorted first. |
| 216 | + """ |
| 217 | + |
| 218 | + def branchIdentities(): |
| 219 | + """A list of aliases for a branch. |
| 220 | + |
| 221 | + Returns a list of tuples of bzr identity and context object. There is |
| 222 | + at least one alias for any branch, and that is the branch itself. For |
| 223 | + linked branches, the context object is the appropriate linked object. |
| 224 | + |
| 225 | + Where a branch is linked to a product series or a suite source |
| 226 | + package, the branch is available through a number of different urls. |
| 227 | + These urls are the aliases for the branch. |
| 228 | + |
| 229 | + For example, a branch linked to the development focus of the 'fooix' |
| 230 | + project is accessible using: |
| 231 | + lp:fooix - the linked object is the product fooix |
| 232 | + lp:fooix/trunk - the linked object is the trunk series of fooix |
| 233 | + lp:~owner/fooix/name - the unique name of the branch where the linked |
| 234 | + object is the branch itself. |
| 235 | + """ |
| 236 | + |
| 237 | # subscription-related methods |
| 238 | @operation_parameters( |
| 239 | person=Reference( |
| 240 | @@ -1303,50 +1334,48 @@ |
| 241 | """ |
| 242 | |
| 243 | |
| 244 | -def bazaar_identity(branch, is_dev_focus): |
| 245 | - """Return the shortest lp: style branch identity.""" |
| 246 | - lp_prefix = config.codehosting.bzr_lp_prefix |
| 247 | - |
| 248 | - # XXX: TimPenhey 2008-05-06 bug=227602: Since at this stage the launchpad |
| 249 | - # name resolution is not authenticated, we can't resolve series branches |
| 250 | - # that end up pointing to private branches, so don't show short names for |
| 251 | - # the branch if it is private. |
| 252 | - if branch.private: |
| 253 | - return lp_prefix + branch.unique_name |
| 254 | - |
| 255 | - use_series = None |
| 256 | - # XXX: JonathanLange 2009-03-21 spec=package-branches: This should |
| 257 | - # probably delegate to IBranch.target. I would do it now if I could figure |
| 258 | - # what all the optimization code is for. |
| 259 | - if branch.product is not None: |
| 260 | - if is_dev_focus: |
| 261 | - return lp_prefix + branch.product.name |
| 262 | - |
| 263 | - # If there are no associated series, then use the unique name. |
| 264 | - associated_series = sorted( |
| 265 | - branch.associatedProductSeries(), key=attrgetter('datecreated')) |
| 266 | - if len(associated_series) == 0: |
| 267 | - return lp_prefix + branch.unique_name |
| 268 | - # Use the most recently created series. |
| 269 | - use_series = associated_series[-1] |
| 270 | - return "%(prefix)s%(product)s/%(series)s" % { |
| 271 | - 'prefix': lp_prefix, |
| 272 | - 'product': use_series.product.name, |
| 273 | - 'series': use_series.name} |
| 274 | - |
| 275 | - if branch.sourcepackage is not None: |
| 276 | - if is_dev_focus: |
| 277 | - return "%(prefix)s%(distro)s/%(packagename)s" % { |
| 278 | - 'prefix': lp_prefix, |
| 279 | - 'distro': branch.distroseries.distribution.name, |
| 280 | - 'packagename': branch.sourcepackagename.name} |
| 281 | - suite_sourcepackages = branch.associatedSuiteSourcePackages() |
| 282 | - # Take the first link if there is one. |
| 283 | - if len(suite_sourcepackages) > 0: |
| 284 | - suite_source_package = suite_sourcepackages[0] |
| 285 | - return lp_prefix + suite_source_package.path |
| 286 | - |
| 287 | - return lp_prefix + branch.unique_name |
| 288 | +class BzrIdentityMixin: |
| 289 | + """This mixin class determines the bazaar identities. |
| 290 | + |
| 291 | + Used by both the model branch class and the browser branch listing item. |
| 292 | + This allows the browser code to cache the associated links which reduces |
| 293 | + query counts. |
| 294 | + """ |
| 295 | + |
| 296 | + @property |
| 297 | + def bzr_identity(self): |
| 298 | + """See `IBranch`.""" |
| 299 | + identity, context = self.branchIdentities()[0] |
| 300 | + return identity |
| 301 | + |
| 302 | + def branchIdentities(self): |
| 303 | + """See `IBranch`.""" |
| 304 | + lp_prefix = config.codehosting.bzr_lp_prefix |
| 305 | + if self.private or not self.target.supports_short_identites: |
| 306 | + # XXX: thumper 2010-04-08, bug 261609 |
| 307 | + # We have to get around to fixing this |
| 308 | + identities = [] |
| 309 | + else: |
| 310 | + identities = [ |
| 311 | + (lp_prefix + link.bzr_path, link.context) |
| 312 | + for link in self.branchLinks()] |
| 313 | + identities.append((lp_prefix + self.unique_name, self)) |
| 314 | + return identities |
| 315 | + |
| 316 | + def branchLinks(self): |
| 317 | + """See `IBranch`.""" |
| 318 | + links = [] |
| 319 | + for suite_sp in self.associatedSuiteSourcePackages(): |
| 320 | + links.append(ICanHasLinkedBranch(suite_sp)) |
| 321 | + if (suite_sp.distribution.currentseries == suite_sp.distroseries |
| 322 | + and suite_sp.pocket == PackagePublishingPocket.RELEASE): |
| 323 | + links.append(ICanHasLinkedBranch( |
| 324 | + suite_sp.sourcepackage.distribution_sourcepackage)) |
| 325 | + for series in self.associatedProductSeries(): |
| 326 | + links.append(ICanHasLinkedBranch(series)) |
| 327 | + if series.product.development_focus == series: |
| 328 | + links.append(ICanHasLinkedBranch(series.product)) |
| 329 | + return sorted(links) |
| 330 | |
| 331 | |
| 332 | def user_has_special_branch_access(user): |
| 333 | |
| 334 | === modified file 'lib/lp/code/interfaces/branchtarget.py' |
| 335 | --- lib/lp/code/interfaces/branchtarget.py 2010-03-26 02:36:01 +0000 |
| 336 | +++ lib/lp/code/interfaces/branchtarget.py 2010-04-12 05:33:38 +0000 |
| 337 | @@ -88,6 +88,9 @@ |
| 338 | supports_merge_proposals = Attribute( |
| 339 | "Does this target support merge proposals at all?") |
| 340 | |
| 341 | + supports_short_identites = Attribute( |
| 342 | + "Does this target support shortened bazaar identities?") |
| 343 | + |
| 344 | supports_code_imports = Attribute( |
| 345 | "Does this target support code imports at all?") |
| 346 | |
| 347 | |
| 348 | === modified file 'lib/lp/code/interfaces/linkedbranch.py' |
| 349 | --- lib/lp/code/interfaces/linkedbranch.py 2009-11-12 04:28:28 +0000 |
| 350 | +++ lib/lp/code/interfaces/linkedbranch.py 2010-04-12 05:33:38 +0000 |
| 351 | @@ -25,6 +25,7 @@ |
| 352 | class ICanHasLinkedBranch(Interface): |
| 353 | """Something that has a linked branch.""" |
| 354 | |
| 355 | + context = Attribute("The object that can have a linked branch.") |
| 356 | branch = Attribute("The linked branch.") |
| 357 | bzr_path = Attribute( |
| 358 | 'The Bazaar branch path for the linked branch. ' |
| 359 | |
| 360 | === modified file 'lib/lp/code/model/branch.py' |
| 361 | --- lib/lp/code/model/branch.py 2010-03-24 18:48:35 +0000 |
| 362 | +++ lib/lp/code/model/branch.py 2010-04-12 05:33:38 +0000 |
| 363 | @@ -62,8 +62,8 @@ |
| 364 | from lp.code.model.seriessourcepackagebranch import SeriesSourcePackageBranch |
| 365 | from lp.code.event.branchmergeproposal import NewBranchMergeProposalEvent |
| 366 | from lp.code.interfaces.branch import ( |
| 367 | - bazaar_identity, BranchCannotBePrivate, BranchCannotBePublic, |
| 368 | - BranchTargetError, BranchTypeError, CannotDeleteBranch, |
| 369 | + BranchCannotBePrivate, BranchCannotBePublic, |
| 370 | + BranchTargetError, BranchTypeError, BzrIdentityMixin, CannotDeleteBranch, |
| 371 | DEFAULT_BRANCH_STATUS_IN_LISTING, IBranch, |
| 372 | IBranchNavigationMenu, IBranchSet, user_has_special_branch_access) |
| 373 | from lp.code.interfaces.branchcollection import IAllBranches |
| 374 | @@ -73,7 +73,6 @@ |
| 375 | from lp.code.interfaces.branchnamespace import IBranchNamespacePolicy |
| 376 | from lp.code.interfaces.branchpuller import IBranchPuller |
| 377 | from lp.code.interfaces.branchtarget import IBranchTarget |
| 378 | -from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch |
| 379 | from lp.code.interfaces.seriessourcepackagebranch import ( |
| 380 | IFindOfficialBranchLinks) |
| 381 | from lp.registry.interfaces.person import ( |
| 382 | @@ -83,7 +82,7 @@ |
| 383 | NotificationRecipientSet) |
| 384 | |
| 385 | |
| 386 | -class Branch(SQLBase): |
| 387 | +class Branch(SQLBase, BzrIdentityMixin): |
| 388 | """A sequence of ordered revisions in Bazaar.""" |
| 389 | |
| 390 | implements(IBranch, IBranchNavigationMenu) |
| 391 | @@ -464,21 +463,6 @@ |
| 392 | def browse_source_url(self): |
| 393 | return self.codebrowse_url('files') |
| 394 | |
| 395 | - @property |
| 396 | - def bzr_identity(self): |
| 397 | - """See `IBranch`.""" |
| 398 | - # Should probably put this into the branch target. |
| 399 | - if self.product is not None: |
| 400 | - series_branch = self.product.development_focus.branch |
| 401 | - is_dev_focus = (series_branch == self) |
| 402 | - elif self.distroseries is not None: |
| 403 | - distro_package = self.sourcepackage.distribution_sourcepackage |
| 404 | - linked_branch = ICanHasLinkedBranch(distro_package) |
| 405 | - is_dev_focus = (linked_branch.branch == self) |
| 406 | - else: |
| 407 | - is_dev_focus = False |
| 408 | - return bazaar_identity(self, is_dev_focus) |
| 409 | - |
| 410 | def composePublicURL(self, scheme='http'): |
| 411 | """See `IBranch`.""" |
| 412 | # Not all protocols work for private branches. |
| 413 | |
| 414 | === modified file 'lib/lp/code/model/branchtarget.py' |
| 415 | --- lib/lp/code/model/branchtarget.py 2010-03-26 02:36:01 +0000 |
| 416 | +++ lib/lp/code/model/branchtarget.py 2010-04-12 05:33:38 +0000 |
| 417 | @@ -103,6 +103,11 @@ |
| 418 | return True |
| 419 | |
| 420 | @property |
| 421 | + def supports_short_identites(self): |
| 422 | + """See `IBranchTarget`.""" |
| 423 | + return True |
| 424 | + |
| 425 | + @property |
| 426 | def supports_code_imports(self): |
| 427 | """See `IBranchTarget`.""" |
| 428 | return False |
| 429 | @@ -195,6 +200,11 @@ |
| 430 | return False |
| 431 | |
| 432 | @property |
| 433 | + def supports_short_identites(self): |
| 434 | + """See `IBranchTarget`.""" |
| 435 | + return False |
| 436 | + |
| 437 | + @property |
| 438 | def supports_code_imports(self): |
| 439 | """See `IBranchTarget`.""" |
| 440 | return False |
| 441 | @@ -276,6 +286,11 @@ |
| 442 | return True |
| 443 | |
| 444 | @property |
| 445 | + def supports_short_identites(self): |
| 446 | + """See `IBranchTarget`.""" |
| 447 | + return True |
| 448 | + |
| 449 | + @property |
| 450 | def supports_code_imports(self): |
| 451 | """See `IBranchTarget`.""" |
| 452 | return True |
| 453 | |
| 454 | === modified file 'lib/lp/code/model/linkedbranch.py' |
| 455 | --- lib/lp/code/model/linkedbranch.py 2009-08-28 06:39:38 +0000 |
| 456 | +++ lib/lp/code/model/linkedbranch.py 2010-04-12 05:33:38 +0000 |
| 457 | @@ -11,6 +11,9 @@ |
| 458 | from zope.component import adapts |
| 459 | from zope.interface import implements |
| 460 | |
| 461 | +from lazr.enum import EnumeratedType, Item |
| 462 | + |
| 463 | +from lp.archivepublisher.debversion import Version |
| 464 | from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch |
| 465 | from lp.registry.interfaces.distributionsourcepackage import ( |
| 466 | IDistributionSourcePackage) |
| 467 | @@ -21,98 +24,194 @@ |
| 468 | from lp.registry.interfaces.suitesourcepackage import ISuiteSourcePackage |
| 469 | |
| 470 | |
| 471 | -class ProductSeriesLinkedBranch: |
| 472 | +class LinkedBranchOrder(EnumeratedType): |
| 473 | + """An enum used only for ordering.""" |
| 474 | + |
| 475 | + PRODUCT = Item('Product') |
| 476 | + DISTRIBUTION_SOURCE_PACKAGE = Item('Distribution Source Package') |
| 477 | + PRODUCT_SERIES = Item('Product Series') |
| 478 | + SUITE_SOURCE_PACKAGE = Item('Suite Source Package') |
| 479 | + |
| 480 | + |
| 481 | +class BaseLinkedBranch: |
| 482 | + """Provides the common sorting algorithm.""" |
| 483 | + |
| 484 | + def __cmp__(self, other): |
| 485 | + if not ICanHasLinkedBranch.providedBy(other): |
| 486 | + raise AssertionError("Can't compare with: %r" % other) |
| 487 | + return cmp(self.sort_order, other.sort_order) |
| 488 | + |
| 489 | + |
| 490 | +class ProductSeriesLinkedBranch(BaseLinkedBranch): |
| 491 | """Implement a linked branch for a product series.""" |
| 492 | |
| 493 | adapts(IProductSeries) |
| 494 | implements(ICanHasLinkedBranch) |
| 495 | |
| 496 | + sort_order = LinkedBranchOrder.PRODUCT_SERIES |
| 497 | + |
| 498 | def __init__(self, product_series): |
| 499 | - self._product_series = product_series |
| 500 | + self.context = product_series |
| 501 | + |
| 502 | + @property |
| 503 | + def product_series(self): |
| 504 | + return self.context |
| 505 | + |
| 506 | + def __cmp__(self, other): |
| 507 | + result = super(ProductSeriesLinkedBranch, self).__cmp__(other) |
| 508 | + if result != 0: |
| 509 | + return result |
| 510 | + else: |
| 511 | + # The sorting of the product series uses the same sorting the |
| 512 | + # product itself uses, which is alphabetically by name. |
| 513 | + my_parts = ( |
| 514 | + self.product_series.product.name, |
| 515 | + self.product_series.name) |
| 516 | + other_parts = ( |
| 517 | + other.product_series.product.name, |
| 518 | + other.product_series.name) |
| 519 | + return cmp(my_parts, other_parts) |
| 520 | |
| 521 | @property |
| 522 | def branch(self): |
| 523 | """See `ICanHasLinkedBranch`.""" |
| 524 | - return self._product_series.branch |
| 525 | + return self.product_series.branch |
| 526 | |
| 527 | @property |
| 528 | def bzr_path(self): |
| 529 | """See `ICanHasLinkedBranch`.""" |
| 530 | return '/'.join( |
| 531 | - [self._product_series.product.name, self._product_series.name]) |
| 532 | + [self.product_series.product.name, self.product_series.name]) |
| 533 | |
| 534 | def setBranch(self, branch, registrant=None): |
| 535 | """See `ICanHasLinkedBranch`.""" |
| 536 | - self._product_series.branch = branch |
| 537 | - |
| 538 | - |
| 539 | -class ProductLinkedBranch: |
| 540 | + self.product_series.branch = branch |
| 541 | + |
| 542 | + |
| 543 | +class ProductLinkedBranch(BaseLinkedBranch): |
| 544 | """Implement a linked branch for a product.""" |
| 545 | |
| 546 | adapts(IProduct) |
| 547 | implements(ICanHasLinkedBranch) |
| 548 | |
| 549 | + sort_order = LinkedBranchOrder.PRODUCT |
| 550 | + |
| 551 | def __init__(self, product): |
| 552 | - self._product = product |
| 553 | + self.context = product |
| 554 | + |
| 555 | + @property |
| 556 | + def product(self): |
| 557 | + return self.context |
| 558 | + |
| 559 | + def __cmp__(self, other): |
| 560 | + result = super(ProductLinkedBranch, self).__cmp__(other) |
| 561 | + if result != 0: |
| 562 | + return result |
| 563 | + else: |
| 564 | + return cmp(self.product.name, other.product.name) |
| 565 | |
| 566 | @property |
| 567 | def branch(self): |
| 568 | """See `ICanHasLinkedBranch`.""" |
| 569 | - return ICanHasLinkedBranch(self._product.development_focus).branch |
| 570 | + return ICanHasLinkedBranch(self.product.development_focus).branch |
| 571 | |
| 572 | @property |
| 573 | def bzr_path(self): |
| 574 | """See `ICanHasLinkedBranch`.""" |
| 575 | - return self._product.name |
| 576 | + return self.product.name |
| 577 | |
| 578 | def setBranch(self, branch, registrant=None): |
| 579 | """See `ICanHasLinkedBranch`.""" |
| 580 | - ICanHasLinkedBranch(self._product.development_focus).setBranch( |
| 581 | + ICanHasLinkedBranch(self.product.development_focus).setBranch( |
| 582 | branch, registrant) |
| 583 | |
| 584 | |
| 585 | -class PackageLinkedBranch: |
| 586 | +class PackageLinkedBranch(BaseLinkedBranch): |
| 587 | """Implement a linked branch for a source package pocket.""" |
| 588 | |
| 589 | adapts(ISuiteSourcePackage) |
| 590 | implements(ICanHasLinkedBranch) |
| 591 | |
| 592 | + sort_order = LinkedBranchOrder.SUITE_SOURCE_PACKAGE |
| 593 | + |
| 594 | def __init__(self, suite_sourcepackage): |
| 595 | - self._suite_sourcepackage = suite_sourcepackage |
| 596 | + self.context = suite_sourcepackage |
| 597 | + |
| 598 | + @property |
| 599 | + def suite_sourcepackage(self): |
| 600 | + return self.context |
| 601 | + |
| 602 | + def __cmp__(self, other): |
| 603 | + result = super(PackageLinkedBranch, self).__cmp__(other) |
| 604 | + if result != 0: |
| 605 | + return result |
| 606 | + # The versions are reversed as we want the greater Version to sort |
| 607 | + # before the lesser one. Hence self in the other tuple, and other in |
| 608 | + # the self tuple. Next compare the distribution name. |
| 609 | + my_parts = ( |
| 610 | + self.suite_sourcepackage.distribution.name, |
| 611 | + Version(other.suite_sourcepackage.distroseries.version), |
| 612 | + self.suite_sourcepackage.sourcepackagename.name, |
| 613 | + self.suite_sourcepackage.pocket) |
| 614 | + other_parts = ( |
| 615 | + other.suite_sourcepackage.distribution.name, |
| 616 | + Version(self.suite_sourcepackage.distroseries.version), |
| 617 | + other.suite_sourcepackage.sourcepackagename.name, |
| 618 | + other.suite_sourcepackage.pocket) |
| 619 | + return cmp(my_parts, other_parts) |
| 620 | |
| 621 | @property |
| 622 | def branch(self): |
| 623 | """See `ICanHasLinkedBranch`.""" |
| 624 | - package = self._suite_sourcepackage.sourcepackage |
| 625 | - pocket = self._suite_sourcepackage.pocket |
| 626 | + package = self.suite_sourcepackage.sourcepackage |
| 627 | + pocket = self.suite_sourcepackage.pocket |
| 628 | return package.getBranch(pocket) |
| 629 | |
| 630 | @property |
| 631 | def bzr_path(self): |
| 632 | """See `ICanHasLinkedBranch`.""" |
| 633 | - return self._suite_sourcepackage.path |
| 634 | + return self.suite_sourcepackage.path |
| 635 | |
| 636 | def setBranch(self, branch, registrant): |
| 637 | """See `ICanHasLinkedBranch`.""" |
| 638 | - package = self._suite_sourcepackage.sourcepackage |
| 639 | - pocket = self._suite_sourcepackage.pocket |
| 640 | + package = self.suite_sourcepackage.sourcepackage |
| 641 | + pocket = self.suite_sourcepackage.pocket |
| 642 | package.setBranch(pocket, branch, registrant) |
| 643 | |
| 644 | |
| 645 | -class DistributionPackageLinkedBranch: |
| 646 | +class DistributionPackageLinkedBranch(BaseLinkedBranch): |
| 647 | """Implement a linked branch for an `IDistributionSourcePackage`.""" |
| 648 | |
| 649 | adapts(IDistributionSourcePackage) |
| 650 | implements(ICanHasLinkedBranch) |
| 651 | |
| 652 | + sort_order = LinkedBranchOrder.DISTRIBUTION_SOURCE_PACKAGE |
| 653 | + |
| 654 | def __init__(self, distribution_sourcepackage): |
| 655 | - self._distribution_sourcepackage = distribution_sourcepackage |
| 656 | + self.context = distribution_sourcepackage |
| 657 | + |
| 658 | + @property |
| 659 | + def distribution_sourcepackage(self): |
| 660 | + return self.context |
| 661 | + |
| 662 | + def __cmp__(self, other): |
| 663 | + result = super(DistributionPackageLinkedBranch, self).__cmp__(other) |
| 664 | + if result != 0: |
| 665 | + return result |
| 666 | + else: |
| 667 | + my_names = ( |
| 668 | + self.distribution_sourcepackage.distribution.name, |
| 669 | + self.distribution_sourcepackage.sourcepackagename.name) |
| 670 | + other_names = ( |
| 671 | + other.distribution_sourcepackage.distribution.name, |
| 672 | + other.distribution_sourcepackage.sourcepackagename.name) |
| 673 | + return cmp(my_names, other_names) |
| 674 | |
| 675 | @property |
| 676 | def branch(self): |
| 677 | """See `ICanHasLinkedBranch`.""" |
| 678 | development_package = ( |
| 679 | - self._distribution_sourcepackage.development_version) |
| 680 | + self.distribution_sourcepackage.development_version) |
| 681 | if development_package is None: |
| 682 | return None |
| 683 | suite_sourcepackage = development_package.getSuiteSourcePackage( |
| 684 | @@ -123,13 +222,13 @@ |
| 685 | def bzr_path(self): |
| 686 | """See `ICanHasLinkedBranch`.""" |
| 687 | return '/'.join( |
| 688 | - [self._distribution_sourcepackage.distribution.name, |
| 689 | - self._distribution_sourcepackage.sourcepackagename.name]) |
| 690 | + [self.distribution_sourcepackage.distribution.name, |
| 691 | + self.distribution_sourcepackage.sourcepackagename.name]) |
| 692 | |
| 693 | def setBranch(self, branch, registrant): |
| 694 | """See `ICanHasLinkedBranch`.""" |
| 695 | development_package = ( |
| 696 | - self._distribution_sourcepackage.development_version) |
| 697 | + self.distribution_sourcepackage.development_version) |
| 698 | if development_package is None: |
| 699 | raise NoSuchDistroSeries('no current series') |
| 700 | suite_sourcepackage = development_package.getSuiteSourcePackage( |
| 701 | |
| 702 | === modified file 'lib/lp/code/model/tests/test_branch.py' |
| 703 | --- lib/lp/code/model/tests/test_branch.py 2010-04-06 17:58:06 +0000 |
| 704 | +++ lib/lp/code/model/tests/test_branch.py 2010-04-12 05:33:38 +0000 |
| 705 | @@ -483,6 +483,203 @@ |
| 706 | self.assertFalse(branch.upgrade_pending) |
| 707 | |
| 708 | |
| 709 | +class TestBranchLinksAndIdentites(TestCaseWithFactory): |
| 710 | + """Test IBranch.branchLinks and IBranch.branchIdentities.""" |
| 711 | + |
| 712 | + layer = DatabaseFunctionalLayer |
| 713 | + |
| 714 | + def test_default_identities(self): |
| 715 | + # If there are no links, the only branch identity is the unique name. |
| 716 | + branch = self.factory.makeAnyBranch() |
| 717 | + self.assertEqual( |
| 718 | + [('lp://dev/' + branch.unique_name, branch)], |
| 719 | + branch.branchIdentities()) |
| 720 | + |
| 721 | + def test_linked_to_product(self): |
| 722 | + # If a branch is linked to the product, it is also by definition |
| 723 | + # linked to the development focus of the product. |
| 724 | + fooix = removeSecurityProxy(self.factory.makeProduct(name='fooix')) |
| 725 | + fooix.development_focus.name = 'devel' |
| 726 | + eric = self.factory.makePerson(name='eric') |
| 727 | + branch = self.factory.makeProductBranch( |
| 728 | + product=fooix, owner=eric, name='trunk') |
| 729 | + linked_branch = ICanHasLinkedBranch(fooix) |
| 730 | + linked_branch.setBranch(branch) |
| 731 | + self.assertEqual( |
| 732 | + [linked_branch, ICanHasLinkedBranch(fooix.development_focus)], |
| 733 | + branch.branchLinks()) |
| 734 | + self.assertEqual( |
| 735 | + [('lp://dev/fooix', fooix), |
| 736 | + ('lp://dev/fooix/devel', fooix.development_focus), |
| 737 | + ('lp://dev/~eric/fooix/trunk', branch)], |
| 738 | + branch.branchIdentities()) |
| 739 | + |
| 740 | + def test_linked_to_product_series(self): |
| 741 | + # If a branch is linked to a non-development series of a product and |
| 742 | + # not linked to the product itself, then only the product series is |
| 743 | + # returned in the links. |
| 744 | + fooix = removeSecurityProxy(self.factory.makeProduct(name='fooix')) |
| 745 | + future = self.factory.makeProductSeries(product=fooix, name='future') |
| 746 | + eric = self.factory.makePerson(name='eric') |
| 747 | + branch = self.factory.makeProductBranch( |
| 748 | + product=fooix, owner=eric, name='trunk') |
| 749 | + linked_branch = ICanHasLinkedBranch(future) |
| 750 | + linked_branch.setBranch(branch) |
| 751 | + self.assertEqual( |
| 752 | + [linked_branch], |
| 753 | + branch.branchLinks()) |
| 754 | + self.assertEqual( |
| 755 | + [('lp://dev/fooix/future', future), |
| 756 | + ('lp://dev/~eric/fooix/trunk', branch)], |
| 757 | + branch.branchIdentities()) |
| 758 | + |
| 759 | + def test_linked_to_package(self): |
| 760 | + # If a branch is linked to a suite source package where the |
| 761 | + # distroseries is the current series for the distribution, there is a |
| 762 | + # link for both the distribution source package and the suite source |
| 763 | + # package. |
| 764 | + mint = self.factory.makeDistribution(name='mint') |
| 765 | + dev = self.factory.makeDistroSeries( |
| 766 | + distribution=mint, version='1.0', name='dev') |
| 767 | + eric = self.factory.makePerson(name='eric') |
| 768 | + branch = self.factory.makePackageBranch( |
| 769 | + distroseries=dev, sourcepackagename='choc', name='tip', |
| 770 | + owner=eric) |
| 771 | + dsp = self.factory.makeDistributionSourcePackage('choc', mint) |
| 772 | + distro_link = ICanHasLinkedBranch(dsp) |
| 773 | + development_package = dsp.development_version |
| 774 | + suite_sourcepackage = development_package.getSuiteSourcePackage( |
| 775 | + PackagePublishingPocket.RELEASE) |
| 776 | + suite_sp_link = ICanHasLinkedBranch(suite_sourcepackage) |
| 777 | + |
| 778 | + registrant = getUtility( |
| 779 | + ILaunchpadCelebrities).ubuntu_branches.teamowner |
| 780 | + run_with_login( |
| 781 | + registrant, |
| 782 | + suite_sp_link.setBranch, branch, registrant) |
| 783 | + |
| 784 | + self.assertEqual( |
| 785 | + [distro_link, suite_sp_link], |
| 786 | + branch.branchLinks()) |
| 787 | + self.assertEqual( |
| 788 | + [('lp://dev/mint/choc', dsp), |
| 789 | + ('lp://dev/mint/dev/choc', suite_sourcepackage), |
| 790 | + ('lp://dev/~eric/mint/dev/choc/tip', branch)], |
| 791 | + branch.branchIdentities()) |
| 792 | + |
| 793 | + def test_linked_to_package_not_release_pocket(self): |
| 794 | + # If a branch is linked to a suite source package where the |
| 795 | + # distroseries is the current series for the distribution, but the |
| 796 | + # pocket is not the RELEASE pocket, then there is only the link for |
| 797 | + # the suite source package. |
| 798 | + mint = self.factory.makeDistribution(name='mint') |
| 799 | + dev = self.factory.makeDistroSeries( |
| 800 | + distribution=mint, version='1.0', name='dev') |
| 801 | + eric = self.factory.makePerson(name='eric') |
| 802 | + branch = self.factory.makePackageBranch( |
| 803 | + distroseries=dev, sourcepackagename='choc', name='tip', |
| 804 | + owner=eric) |
| 805 | + dsp = self.factory.makeDistributionSourcePackage('choc', mint) |
| 806 | + development_package = dsp.development_version |
| 807 | + suite_sourcepackage = development_package.getSuiteSourcePackage( |
| 808 | + PackagePublishingPocket.BACKPORTS) |
| 809 | + suite_sp_link = ICanHasLinkedBranch(suite_sourcepackage) |
| 810 | + |
| 811 | + registrant = getUtility( |
| 812 | + ILaunchpadCelebrities).ubuntu_branches.teamowner |
| 813 | + run_with_login( |
| 814 | + registrant, |
| 815 | + suite_sp_link.setBranch, branch, registrant) |
| 816 | + |
| 817 | + self.assertEqual( |
| 818 | + [suite_sp_link], |
| 819 | + branch.branchLinks()) |
| 820 | + self.assertEqual( |
| 821 | + [('lp://dev/mint/dev-backports/choc', suite_sourcepackage), |
| 822 | + ('lp://dev/~eric/mint/dev/choc/tip', branch)], |
| 823 | + branch.branchIdentities()) |
| 824 | + |
| 825 | + def test_linked_to_package_not_current_series(self): |
| 826 | + # If the branch is linked to a suite source package where the distro |
| 827 | + # series is not the current series, only the suite source package is |
| 828 | + # returned in the links. |
| 829 | + mint = self.factory.makeDistribution(name='mint') |
| 830 | + self.factory.makeDistroSeries( |
| 831 | + distribution=mint, version='1.0', name='dev') |
| 832 | + supported = self.factory.makeDistroSeries( |
| 833 | + distribution=mint, version='0.9', name='supported') |
| 834 | + eric = self.factory.makePerson(name='eric') |
| 835 | + branch = self.factory.makePackageBranch( |
| 836 | + distroseries=supported, sourcepackagename='choc', name='tip', |
| 837 | + owner=eric) |
| 838 | + suite_sp = self.factory.makeSuiteSourcePackage( |
| 839 | + distroseries=supported, sourcepackagename='choc', |
| 840 | + pocket=PackagePublishingPocket.RELEASE) |
| 841 | + suite_sp_link = ICanHasLinkedBranch(suite_sp) |
| 842 | + |
| 843 | + registrant = getUtility( |
| 844 | + ILaunchpadCelebrities).ubuntu_branches.teamowner |
| 845 | + run_with_login( |
| 846 | + registrant, |
| 847 | + suite_sp_link.setBranch, branch, registrant) |
| 848 | + |
| 849 | + self.assertEqual( |
| 850 | + [suite_sp_link], |
| 851 | + branch.branchLinks()) |
| 852 | + self.assertEqual( |
| 853 | + [('lp://dev/mint/supported/choc', suite_sp), |
| 854 | + ('lp://dev/~eric/mint/supported/choc/tip', branch)], |
| 855 | + branch.branchIdentities()) |
| 856 | + |
| 857 | + def test_linked_across_project_to_package(self): |
| 858 | + # If a product branch is linked to a suite source package, the links |
| 859 | + # are the same as if it was a source package branch. |
| 860 | + mint = self.factory.makeDistribution(name='mint') |
| 861 | + self.factory.makeDistroSeries( |
| 862 | + distribution=mint, version='1.0', name='dev') |
| 863 | + eric = self.factory.makePerson(name='eric') |
| 864 | + fooix = self.factory.makeProduct(name='fooix') |
| 865 | + branch = self.factory.makeProductBranch( |
| 866 | + product=fooix, owner=eric, name='trunk') |
| 867 | + dsp = self.factory.makeDistributionSourcePackage('choc', mint) |
| 868 | + distro_link = ICanHasLinkedBranch(dsp) |
| 869 | + development_package = dsp.development_version |
| 870 | + suite_sourcepackage = development_package.getSuiteSourcePackage( |
| 871 | + PackagePublishingPocket.RELEASE) |
| 872 | + suite_sp_link = ICanHasLinkedBranch(suite_sourcepackage) |
| 873 | + |
| 874 | + registrant = getUtility( |
| 875 | + ILaunchpadCelebrities).ubuntu_branches.teamowner |
| 876 | + run_with_login( |
| 877 | + registrant, |
| 878 | + suite_sp_link.setBranch, branch, registrant) |
| 879 | + |
| 880 | + self.assertEqual( |
| 881 | + [distro_link, suite_sp_link], |
| 882 | + branch.branchLinks()) |
| 883 | + self.assertEqual( |
| 884 | + [('lp://dev/mint/choc', dsp), |
| 885 | + ('lp://dev/mint/dev/choc', suite_sourcepackage), |
| 886 | + ('lp://dev/~eric/fooix/trunk', branch)], |
| 887 | + branch.branchIdentities()) |
| 888 | + |
| 889 | + def test_junk_branch_links(self): |
| 890 | + # If a junk branch has links, those links are returned in the |
| 891 | + # branchLinks, but the branchIdentities just has the branch unique |
| 892 | + # name. |
| 893 | + eric = self.factory.makePerson(name='eric') |
| 894 | + branch = self.factory.makePersonalBranch(owner=eric, name='foo') |
| 895 | + fooix = removeSecurityProxy(self.factory.makeProduct()) |
| 896 | + linked_branch = ICanHasLinkedBranch(fooix) |
| 897 | + linked_branch.setBranch(branch) |
| 898 | + self.assertEqual( |
| 899 | + [linked_branch, ICanHasLinkedBranch(fooix.development_focus)], |
| 900 | + branch.branchLinks()) |
| 901 | + self.assertEqual( |
| 902 | + [('lp://dev/~eric/+junk/foo', branch)], |
| 903 | + branch.branchIdentities()) |
| 904 | + |
| 905 | + |
| 906 | class TestBzrIdentity(TestCaseWithFactory): |
| 907 | """Test IBranch.bzr_identity.""" |
| 908 | |
| 909 | |
| 910 | === modified file 'lib/lp/code/model/tests/test_branchtarget.py' |
| 911 | --- lib/lp/code/model/tests/test_branchtarget.py 2010-03-18 19:57:20 +0000 |
| 912 | +++ lib/lp/code/model/tests/test_branchtarget.py 2010-04-12 05:33:38 +0000 |
| 913 | @@ -122,6 +122,10 @@ |
| 914 | # Package branches do support merge proposals. |
| 915 | self.assertTrue(self.target.supports_merge_proposals) |
| 916 | |
| 917 | + def test_supports_short_identites(self): |
| 918 | + # Package branches do support short bzr identites. |
| 919 | + self.assertTrue(self.target.supports_short_identites) |
| 920 | + |
| 921 | def test_displayname(self): |
| 922 | # The display name of a source package target is the display name of |
| 923 | # the source package. |
| 924 | @@ -228,6 +232,10 @@ |
| 925 | # Personal branches do not support merge proposals. |
| 926 | self.assertFalse(self.target.supports_merge_proposals) |
| 927 | |
| 928 | + def test_supports_short_identites(self): |
| 929 | + # Personal branches do not support short bzr identites. |
| 930 | + self.assertFalse(self.target.supports_short_identites) |
| 931 | + |
| 932 | def test_displayname(self): |
| 933 | # The display name of a person branch target is ~$USER/+junk. |
| 934 | target = IBranchTarget(self.original) |
| 935 | @@ -341,6 +349,10 @@ |
| 936 | # Product branches do support merge proposals. |
| 937 | self.assertTrue(self.target.supports_merge_proposals) |
| 938 | |
| 939 | + def test_supports_short_identites(self): |
| 940 | + # Product branches do support short bzr identites. |
| 941 | + self.assertTrue(self.target.supports_short_identites) |
| 942 | + |
| 943 | def test_displayname(self): |
| 944 | # The display name of a product branch target is the display name of |
| 945 | # the product. |
| 946 | |
| 947 | === modified file 'lib/lp/code/model/tests/test_linkedbranch.py' |
| 948 | --- lib/lp/code/model/tests/test_linkedbranch.py 2009-08-28 06:39:38 +0000 |
| 949 | +++ lib/lp/code/model/tests/test_linkedbranch.py 2010-04-12 05:33:38 +0000 |
| 950 | @@ -201,5 +201,149 @@ |
| 951 | CannotHaveLinkedBranch, get_linked_branch, project) |
| 952 | |
| 953 | |
| 954 | +class TestLinkedBranchSorting(TestCaseWithFactory): |
| 955 | + |
| 956 | + layer = DatabaseFunctionalLayer |
| 957 | + |
| 958 | + def test_sorting_different_types(self): |
| 959 | + # The different types can be sorted together, and sort so that the |
| 960 | + # results are ordered like: |
| 961 | + # Product Link |
| 962 | + # Distribution Source Package Link |
| 963 | + # Product Series Link |
| 964 | + # Package Link |
| 965 | + product_link = ICanHasLinkedBranch(self.factory.makeProduct()) |
| 966 | + product_series_link = ICanHasLinkedBranch( |
| 967 | + self.factory.makeProductSeries()) |
| 968 | + distro_sp_link = ICanHasLinkedBranch( |
| 969 | + self.factory.makeDistributionSourcePackage()) |
| 970 | + package_link = ICanHasLinkedBranch( |
| 971 | + self.factory.makeSuiteSourcePackage()) |
| 972 | + |
| 973 | + links = sorted( |
| 974 | + [package_link, product_series_link, distro_sp_link, product_link]) |
| 975 | + self.assertIs(product_link, links[0]) |
| 976 | + self.assertIs(distro_sp_link, links[1]) |
| 977 | + self.assertIs(product_series_link, links[2]) |
| 978 | + self.assertIs(package_link, links[3]) |
| 979 | + |
| 980 | + def test_product_sort(self): |
| 981 | + # If in the extremely unlikely event we have one branch linked as the |
| 982 | + # trunk of two or more different products (you never know), then the |
| 983 | + # sorting reverts to the name of the product. |
| 984 | + aardvark_link = ICanHasLinkedBranch( |
| 985 | + self.factory.makeProduct(name='aardvark')) |
| 986 | + meerkat_link = ICanHasLinkedBranch( |
| 987 | + self.factory.makeProduct(name='meerkat')) |
| 988 | + zebra_link = ICanHasLinkedBranch( |
| 989 | + self.factory.makeProduct(name='zebra')) |
| 990 | + links = sorted( |
| 991 | + [zebra_link, aardvark_link, meerkat_link]) |
| 992 | + self.assertIs(aardvark_link, links[0]) |
| 993 | + self.assertIs(meerkat_link, links[1]) |
| 994 | + self.assertIs(zebra_link, links[2]) |
| 995 | + |
| 996 | + def test_product_series_sort(self): |
| 997 | + # Sorting by product series checks the product name first, then series |
| 998 | + # name. |
| 999 | + aardvark = self.factory.makeProduct(name='aardvark') |
| 1000 | + zebra = self.factory.makeProduct(name='zebra') |
| 1001 | + aardvark_devel = ICanHasLinkedBranch( |
| 1002 | + self.factory.makeProductSeries( |
| 1003 | + product=aardvark, name='devel')) |
| 1004 | + aardvark_testing = ICanHasLinkedBranch( |
| 1005 | + self.factory.makeProductSeries( |
| 1006 | + product=aardvark, name='testing')) |
| 1007 | + zebra_devel = ICanHasLinkedBranch( |
| 1008 | + self.factory.makeProductSeries( |
| 1009 | + product=zebra, name='devel')) |
| 1010 | + zebra_mashup = ICanHasLinkedBranch( |
| 1011 | + self.factory.makeProductSeries( |
| 1012 | + product=zebra, name='mashup')) |
| 1013 | + |
| 1014 | + links = sorted( |
| 1015 | + [zebra_mashup, aardvark_testing, zebra_devel, aardvark_devel]) |
| 1016 | + self.assertIs(aardvark_devel, links[0]) |
| 1017 | + self.assertIs(aardvark_testing, links[1]) |
| 1018 | + self.assertIs(zebra_devel, links[2]) |
| 1019 | + self.assertIs(zebra_mashup, links[3]) |
| 1020 | + |
| 1021 | + def test_distribution_source_package_sort(self): |
| 1022 | + # Sorting of distribution source packages sorts firstly on the |
| 1023 | + # distribution name, then the package name. |
| 1024 | + aardvark = self.factory.makeDistribution(name='aardvark') |
| 1025 | + zebra = self.factory.makeDistribution(name='zebra') |
| 1026 | + aardvark_devel = ICanHasLinkedBranch( |
| 1027 | + self.factory.makeDistributionSourcePackage( |
| 1028 | + distribution=aardvark, sourcepackagename='devel')) |
| 1029 | + aardvark_testing = ICanHasLinkedBranch( |
| 1030 | + self.factory.makeDistributionSourcePackage( |
| 1031 | + distribution=aardvark, sourcepackagename='testing')) |
| 1032 | + zebra_devel = ICanHasLinkedBranch( |
| 1033 | + self.factory.makeDistributionSourcePackage( |
| 1034 | + distribution=zebra, sourcepackagename='devel')) |
| 1035 | + zebra_mashup = ICanHasLinkedBranch( |
| 1036 | + self.factory.makeDistributionSourcePackage( |
| 1037 | + distribution=zebra, sourcepackagename='mashup')) |
| 1038 | + |
| 1039 | + links = sorted( |
| 1040 | + [zebra_mashup, aardvark_testing, zebra_devel, aardvark_devel]) |
| 1041 | + self.assertIs(aardvark_devel, links[0]) |
| 1042 | + self.assertIs(aardvark_testing, links[1]) |
| 1043 | + self.assertIs(zebra_devel, links[2]) |
| 1044 | + self.assertIs(zebra_mashup, links[3]) |
| 1045 | + |
| 1046 | + def test_suite_source_package_sort(self): |
| 1047 | + # The sorting of suite source packages checks the distribution first, |
| 1048 | + # then the distroseries version, followed by the source package name, |
| 1049 | + # and finally the pocket. |
| 1050 | + aardvark = ICanHasLinkedBranch( |
| 1051 | + self.factory.makeSuiteSourcePackage( |
| 1052 | + distroseries=self.factory.makeDistroSeries( |
| 1053 | + self.factory.makeDistribution(name='aardvark')))) |
| 1054 | + zebra = ICanHasLinkedBranch( |
| 1055 | + self.factory.makeSuiteSourcePackage( |
| 1056 | + distroseries=self.factory.makeDistroSeries( |
| 1057 | + self.factory.makeDistribution(name='zebra')))) |
| 1058 | + meerkat = self.factory.makeDistribution(name='meerkat') |
| 1059 | + meerkat_1 = ICanHasLinkedBranch( |
| 1060 | + self.factory.makeSuiteSourcePackage( |
| 1061 | + self.factory.makeDistroSeries(meerkat, "1.0"))) |
| 1062 | + meerkat_2 = self.factory.makeDistroSeries(meerkat, "2.0") |
| 1063 | + meerkat_3 = ICanHasLinkedBranch( |
| 1064 | + self.factory.makeSuiteSourcePackage( |
| 1065 | + self.factory.makeDistroSeries(meerkat, "3.0"))) |
| 1066 | + meerkat_2_devel_release = ICanHasLinkedBranch( |
| 1067 | + self.factory.makeSuiteSourcePackage( |
| 1068 | + meerkat_2, 'devel', PackagePublishingPocket.RELEASE)) |
| 1069 | + meerkat_2_devel_updates = ICanHasLinkedBranch( |
| 1070 | + self.factory.makeSuiteSourcePackage( |
| 1071 | + meerkat_2, 'devel', PackagePublishingPocket.UPDATES)) |
| 1072 | + meerkat_2_devel_backports = ICanHasLinkedBranch( |
| 1073 | + self.factory.makeSuiteSourcePackage( |
| 1074 | + meerkat_2, 'devel', PackagePublishingPocket.BACKPORTS)) |
| 1075 | + meerkat_2_apples = ICanHasLinkedBranch( |
| 1076 | + self.factory.makeSuiteSourcePackage( |
| 1077 | + meerkat_2, 'apples')) |
| 1078 | + |
| 1079 | + links = sorted( |
| 1080 | + [meerkat_3, |
| 1081 | + meerkat_2_devel_updates, |
| 1082 | + zebra, |
| 1083 | + meerkat_2_apples, |
| 1084 | + aardvark, |
| 1085 | + meerkat_2_devel_backports, |
| 1086 | + meerkat_1, |
| 1087 | + meerkat_2_devel_release]) |
| 1088 | + self.assertIs(aardvark, links[0]) |
| 1089 | + self.assertIs(meerkat_3, links[1]) |
| 1090 | + self.assertIs(meerkat_2_apples, links[2]) |
| 1091 | + self.assertIs(meerkat_2_devel_release, links[3]) |
| 1092 | + self.assertIs(meerkat_2_devel_updates, links[4]) |
| 1093 | + self.assertIs(meerkat_2_devel_backports, links[5]) |
| 1094 | + self.assertIs(meerkat_1, links[6]) |
| 1095 | + self.assertIs(zebra, links[7]) |
| 1096 | + |
| 1097 | + |
| 1098 | def test_suite(): |
| 1099 | return unittest.TestLoader().loadTestsFromName(__name__) |
| 1100 | |
| 1101 | === modified file 'lib/lp/code/stories/branches/xx-branch-listings.txt' |
| 1102 | --- lib/lp/code/stories/branches/xx-branch-listings.txt 2009-08-14 18:02:29 +0000 |
| 1103 | +++ lib/lp/code/stories/branches/xx-branch-listings.txt 2010-04-12 05:33:38 +0000 |
| 1104 | @@ -365,8 +365,7 @@ |
| 1105 | |
| 1106 | Now when we look at the branches for gnome-terminal, the main branch |
| 1107 | now shows as the "focus of development". This is indicated by |
| 1108 | -both the series link in the first column with the branch unique name, |
| 1109 | -but also with a star with the badges. |
| 1110 | +both the series link in the first column with the branch unique name. |
| 1111 | |
| 1112 | >>> browser.open('http://code.launchpad.dev/gnome-terminal') |
| 1113 | >>> table = find_tag_by_id(browser.contents, 'branchtable') |
| 1114 | @@ -375,8 +374,6 @@ |
| 1115 | >>> cols = row.fetch('td') |
| 1116 | >>> print extract_text(cols[0]) |
| 1117 | lp://dev/gnome-terminal Series: trunk |
| 1118 | - >>> print cols[1].renderContents() |
| 1119 | - <img src="/@@/favourite-yes" title="focus of development" /> |
| 1120 | |
| 1121 | If a branch is associated with more than one series, then the links |
| 1122 | are comma separated and in alphabetical order. |
| 1123 | |
| 1124 | === modified file 'lib/lp/code/templates/branch-listing.pt' |
| 1125 | --- lib/lp/code/templates/branch-listing.pt 2009-12-03 18:33:22 +0000 |
| 1126 | +++ lib/lp/code/templates/branch-listing.pt 2010-04-12 05:33:38 +0000 |
| 1127 | @@ -41,159 +41,6 @@ |
| 1128 | } |
| 1129 | registerLaunchpadFunction(hookUpFilterSubmission); |
| 1130 | |
| 1131 | -LPS.use('io-base', 'node', 'json-parse', function(Y) { |
| 1132 | - |
| 1133 | -function doUpdate(transaction_id, response, args) { |
| 1134 | - json_values = Y.JSON.parse(response.responseText); |
| 1135 | - // Make sure that the branch isn't empty, and that there |
| 1136 | - // have been commits in the last 90 days |
| 1137 | - |
| 1138 | - // Logic has been inverted in the next line to avoid |
| 1139 | - // breaking XHTML compliance of the template due to |
| 1140 | - // ampersand usage. |
| 1141 | - if(!(!json_values.commits || !json_values.count > 0)) { |
| 1142 | - document.getElementById(args.container).innerHTML = ''; |
| 1143 | - sparkline(args.container, json_values); |
| 1144 | - } |
| 1145 | -} |
| 1146 | - |
| 1147 | - |
| 1148 | -function renderSpark() { |
| 1149 | - |
| 1150 | - for (spark_div in branch_sparks) { |
| 1151 | - |
| 1152 | - spark_div = branch_sparks[spark_div][0]; |
| 1153 | - |
| 1154 | - if (Y.one('#' + spark_div) !== null) { |
| 1155 | - json_url = branch_sparks[0][1]; |
| 1156 | - var container = spark_div; |
| 1157 | - var uri = json_url; |
| 1158 | - var xhr_config = { |
| 1159 | - on: { |
| 1160 | - success: doUpdate, |
| 1161 | - }, |
| 1162 | - arguments: { |
| 1163 | - 'container': container |
| 1164 | - } |
| 1165 | - }; |
| 1166 | - |
| 1167 | - Y.io(uri, xhr_config); |
| 1168 | - } |
| 1169 | - } |
| 1170 | -} |
| 1171 | - |
| 1172 | - |
| 1173 | -function sparkline(branch_id, o) { |
| 1174 | - var spark_div = document.getElementById(branch_id); |
| 1175 | - var number_commits = parseInt(o.commits.length); |
| 1176 | - var w = 160; |
| 1177 | - var min = 9999; |
| 1178 | - var max = -1; |
| 1179 | - var h = 12; |
| 1180 | - var co = document.createElement("canvas"); |
| 1181 | - |
| 1182 | - co.style.height = h; |
| 1183 | - co.style.width = w; |
| 1184 | - co.width = w; |
| 1185 | - co.height = h; |
| 1186 | - |
| 1187 | - spark_div.appendChild(co); |
| 1188 | - |
| 1189 | - // Figure out the max and min number of commits |
| 1190 | - |
| 1191 | - // Logic has been inverted in the next line to avoid breaking |
| 1192 | - // XHTML compliance of the template due to ``less than`` symbol |
| 1193 | - // usage (using ``greater than`` is fine). |
| 1194 | - for (var i = 0; number_commits > i; i++) { |
| 1195 | - o.commits[i] = o.commits[i] - 0; |
| 1196 | - if ( min > o.commits[i] ) min = o.commits[i]; |
| 1197 | - if ( o.commits[i] > max ) max = o.commits[i]; |
| 1198 | - } |
| 1199 | - |
| 1200 | - if (co.getContext) { |
| 1201 | - var c = co.getContext("2d"); |
| 1202 | - c.strokeStyle = "black"; |
| 1203 | - c.lineWidth = 1.0; |
| 1204 | - c.beginPath(); |
| 1205 | - |
| 1206 | - // Logic has been inverted in the next line to avoid breaking |
| 1207 | - // XHTML compliance of the template due to ``less than`` symbol |
| 1208 | - // usage (using ``greater than`` is fine). |
| 1209 | - for (var i = 0; number_commits > i; i++) { |
| 1210 | - x_pos = (w / number_commits) * i; |
| 1211 | - // We subtract 1 pixel here to compensate for the base line |
| 1212 | - y_pos = (h-1) - (((o.commits[i] - min) / (max - min)) * (h-1)); |
| 1213 | - |
| 1214 | - if (i == 0) { |
| 1215 | - c.moveTo(x_pos, y_pos); |
| 1216 | - } |
| 1217 | - c.lineTo(x_pos, y_pos); |
| 1218 | - |
| 1219 | - // When was the last commit? |
| 1220 | - if(o.commits[i] > 0) { |
| 1221 | - last_commit = i+1; |
| 1222 | - } |
| 1223 | - |
| 1224 | - } |
| 1225 | - c.stroke(); |
| 1226 | - |
| 1227 | - // Draw the base line |
| 1228 | - c.beginPath(); |
| 1229 | - c.strokeStyle = "gray"; |
| 1230 | - c.moveTo(0,h); |
| 1231 | - c.lineTo(w,h); |
| 1232 | - c.stroke(); |
| 1233 | - |
| 1234 | - // Marks the point with the most commits |
| 1235 | - c.fillStyle = 'red'; |
| 1236 | - c.beginPath(); |
| 1237 | - c.arc((w / number_commits) * o.max_commits, |
| 1238 | - h+2 - (((o.commits[o.max_commits] - min) / (max - min)) * h), |
| 1239 | - 2, 0, (Math.PI*2), true); |
| 1240 | - c.fill(); |
| 1241 | - |
| 1242 | - // Shows the number of max commits |
| 1243 | - new_text = document.createTextNode(o.commits[o.max_commits] + ' max commits'); |
| 1244 | - new_div = document.createElement('div'); |
| 1245 | - new_div.setAttribute('id', 'txt_max_' + branch_id); |
| 1246 | - new_div.appendChild(new_text); |
| 1247 | - document.getElementById(branch_id).appendChild(new_div); |
| 1248 | - new_div.style.fontSize = '8px'; |
| 1249 | - new_div.style.color = 'red'; |
| 1250 | - new_div.style.position = 'relative'; |
| 1251 | - new_div.style.top = '-22px'; |
| 1252 | - new_div.style.left = (((w / number_commits) * o.max_commits)-14) + 'px'; |
| 1253 | - |
| 1254 | - |
| 1255 | - // Marks the point of last commit |
| 1256 | - c.fillStyle = 'blue'; |
| 1257 | - c.beginPath(); |
| 1258 | - c.arc((w / number_commits) * last_commit, h-2, |
| 1259 | - 2, 0, (Math.PI*2), true); |
| 1260 | - c.fill(); |
| 1261 | - |
| 1262 | - // Shows the date of the last commit and time frame |
| 1263 | - new_text = document.createTextNode('90 days - Last commit ' + o.last_commit); |
| 1264 | - new_div = document.createElement('div'); |
| 1265 | - new_div.setAttribute('id', 'txt_' + branch_id); |
| 1266 | - new_div.appendChild(new_text); |
| 1267 | - document.getElementById(branch_id).appendChild(new_div); |
| 1268 | - new_div.style.fontSize = '8px'; |
| 1269 | - new_div.style.color = 'blue'; |
| 1270 | - new_div.style.position = 'relative'; |
| 1271 | - new_div.style.left = '2px'; |
| 1272 | - new_div.style.top = '-10px'; |
| 1273 | - |
| 1274 | - } |
| 1275 | -} |
| 1276 | - |
| 1277 | -// XXX TimPenhey 2009-08-03 bug 408207 - disable sparklines |
| 1278 | -// Y.on("domready", renderSpark); |
| 1279 | - |
| 1280 | -}); |
| 1281 | - |
| 1282 | - |
| 1283 | - |
| 1284 | </script> |
| 1285 | |
| 1286 | <tal:needs-batch condition="context/has_multiple_pages"> |
| 1287 | @@ -255,9 +102,6 @@ |
| 1288 | </tal:associated-series> |
| 1289 | </td> |
| 1290 | <td align="right" style="padding-right: 5px"> |
| 1291 | - <tal:dev_focus condition="branch/is_development_focus"> |
| 1292 | - <img src="/@@/favourite-yes" title="focus of development"/> |
| 1293 | - </tal:dev_focus> |
| 1294 | <tal:badges replace="structure branch/badges:small"/> |
| 1295 | </td> |
| 1296 | <td> |
| 1297 | @@ -292,15 +136,10 @@ |
| 1298 | tal:content="branch/date_last_modified/fmt:datetime"> |
| 1299 | 2005-02-12 13:45 EST |
| 1300 | </span> |
| 1301 | - <div id="dev_focus_spark" style="top:10px; position:relative; height:18px;" |
| 1302 | - tal:omit-tag="not: branch/is_development_focus"> |
| 1303 | - <div tal:attributes="id string:b-${repeat/branch/number}"> |
| 1304 | - <span tal:attributes="title branch/date_last_modified/fmt:datetime" |
| 1305 | - tal:content="branch/since_updated/fmt:approximateduration/use-digits"> |
| 1306 | - sometime |
| 1307 | - </span> ago |
| 1308 | - </div> |
| 1309 | - </div> |
| 1310 | + <span tal:attributes="title branch/date_last_modified/fmt:datetime" |
| 1311 | + tal:content="branch/since_updated/fmt:approximateduration/use-digits"> |
| 1312 | + sometime |
| 1313 | + </span> ago |
| 1314 | </td> |
| 1315 | |
| 1316 | <tal:no_commit condition="not: branch/last_commit"> |
| 1317 | @@ -368,10 +207,3 @@ |
| 1318 | <tal:navigation replace="structure context/@@+navigation-links-lower" /> |
| 1319 | |
| 1320 | </div> |
| 1321 | - |
| 1322 | -<tal:script |
| 1323 | - replace="structure string:<script id='branch-sparks' type='text/javascript'>" /> |
| 1324 | -<tal:omit tal:omit-tag=""> |
| 1325 | -var branch_sparks = <tal:array replace="context/branch_sparks"/>; |
| 1326 | -</tal:omit> |
| 1327 | -<tal:script replace="structure string:</script>" /> |
| 1328 | |
| 1329 | === modified file 'lib/lp/testing/factory.py' |
| 1330 | --- lib/lp/testing/factory.py 2010-04-08 17:17:15 +0000 |
| 1331 | +++ lib/lp/testing/factory.py 2010-04-12 05:33:38 +0000 |
| 1332 | @@ -1773,8 +1773,11 @@ |
| 1333 | owner = self.makePerson() |
| 1334 | if distroseries is None: |
| 1335 | distroseries = self.makeDistroSeries() |
| 1336 | - if sourcepackagename is None: |
| 1337 | - sourcepackagename = self.makeSourcePackageName() |
| 1338 | + # Make sure we have a real sourcepackagename object. |
| 1339 | + if (sourcepackagename is None or |
| 1340 | + isinstance(sourcepackagename, basestring)): |
| 1341 | + sourcepackagename = self.getOrMakeSourcePackageName( |
| 1342 | + sourcepackagename) |
| 1343 | if name is None: |
| 1344 | name = self.getUniqueString().decode('utf8') |
| 1345 | if description is None: |
| 1346 | @@ -1789,7 +1792,7 @@ |
| 1347 | sourcename=None): |
| 1348 | """Make a new SourcePackageRecipeBuild.""" |
| 1349 | if sourcepackage is None: |
| 1350 | - sourcepackage = self.makeSourcePackage(sourcename=sourcename) |
| 1351 | + sourcepackage = self.makeSourcePackage(sourcename) |
| 1352 | if recipe is None: |
| 1353 | recipe = self.makeSourcePackageRecipe() |
| 1354 | if requester is None: |
| 1355 | @@ -2061,11 +2064,13 @@ |
| 1356 | return self.makeSourcePackageName() |
| 1357 | return getUtility(ISourcePackageNameSet).getOrCreateByName(name) |
| 1358 | |
| 1359 | - def makeSourcePackage( |
| 1360 | - self, sourcepackagename=None, distroseries=None, sourcename=None): |
| 1361 | + def makeSourcePackage(self, sourcepackagename=None, distroseries=None): |
| 1362 | """Make an `ISourcePackage`.""" |
| 1363 | - if sourcepackagename is None: |
| 1364 | - sourcepackagename = self.makeSourcePackageName(sourcename) |
| 1365 | + # Make sure we have a real sourcepackagename object. |
| 1366 | + if (sourcepackagename is None or |
| 1367 | + isinstance(sourcepackagename, basestring)): |
| 1368 | + sourcepackagename = self.getOrMakeSourcePackageName( |
| 1369 | + sourcepackagename) |
| 1370 | if distroseries is None: |
| 1371 | distroseries = self.makeDistroRelease() |
| 1372 | return distroseries.getSourcePackage(sourcepackagename) |
| 1373 | @@ -2249,14 +2254,20 @@ |
| 1374 | distroseries = self.makeDistroRelease() |
| 1375 | if pocket is None: |
| 1376 | pocket = self.getAnyPocket() |
| 1377 | - if sourcepackagename is None: |
| 1378 | - sourcepackagename = self.makeSourcePackageName() |
| 1379 | + # Make sure we have a real sourcepackagename object. |
| 1380 | + if (sourcepackagename is None or |
| 1381 | + isinstance(sourcepackagename, basestring)): |
| 1382 | + sourcepackagename = self.getOrMakeSourcePackageName( |
| 1383 | + sourcepackagename) |
| 1384 | return SuiteSourcePackage(distroseries, pocket, sourcepackagename) |
| 1385 | |
| 1386 | def makeDistributionSourcePackage(self, sourcepackagename=None, |
| 1387 | distribution=None): |
| 1388 | - if sourcepackagename is None: |
| 1389 | - sourcepackagename = self.makeSourcePackageName() |
| 1390 | + # Make sure we have a real sourcepackagename object. |
| 1391 | + if (sourcepackagename is None or |
| 1392 | + isinstance(sourcepackagename, basestring)): |
| 1393 | + sourcepackagename = self.getOrMakeSourcePackageName( |
| 1394 | + sourcepackagename) |
| 1395 | if distribution is None: |
| 1396 | distribution = self.makeDistribution() |
| 1397 |

Overall, yay, this branch makes things simpler and more correct.
The mixin is still a bit odd, but hopefully noone will have to touch
this code for a few years now :-)
Just a few comments below, mostly simple clarity stuff. Thanks for
fixing the icky bug!
Cheers,
mwh
> === modified file 'lib/lp/ code/interfaces /branch. py' code/interfaces /branch. py 2010-04-06 17:19:23 +0000 code/interfaces /branch. py 2010-04-08 04:14:00 +0000 NAME_VALIDATION _ERROR_ MESSAGE' , name_validator' , Private' , ror', anch', BRANCH_ STATUS_ IN_LISTING' , d_hostnames' , ionMenu' , ypeError' , special_ branch_ access' , interfaces. branchlookup import IBranchLookup interfaces. branchtarget import IHasBranchTarget interfaces. linkedbranch import ICanHasLinkedBranch interfaces. hasbranches import IHasMergeProposals interfaces. hasrecipes import IHasRecipes launchpad. interfaces. launchpad import ( rities, IPrivacy) interfaces. role import IHasOwner interfaces. person import IPerson interfaces. pocket import PackagePublishi ngPocket launchpad. webapp. interfaces import ( gator, NameLookupFailed) launchpad. webapp. menu import structured
> --- lib/lp/
> +++ lib/lp/
> @@ -8,7 +8,6 @@
> __metaclass__ = type
>
> __all__ = [
> - 'bazaar_identity',
> 'BRANCH_
> 'branch_
> 'BranchCannotBe
> @@ -21,6 +20,7 @@
> 'BranchExists',
> 'BranchTargetEr
> 'BranchTypeError',
> + 'BzrIdentityMixin',
> 'CannotDeleteBr
> 'DEFAULT_
> 'get_blackliste
> @@ -32,12 +32,10 @@
> 'IBranchNavigat
> 'IBranchSet',
> 'NoSuchBranch',
> - 'UnknownBranchT
> 'user_has_
> ]
>
> from cgi import escape
> -from operator import attrgetter
> import re
>
> from zope.component import getUtility
> @@ -69,12 +67,14 @@
> )
> from lp.code.
> from lp.code.
> +from lp.code.
> from lp.code.
> from lp.code.
> from canonical.
> ILaunchpadCeleb
> from lp.registry.
> from lp.registry.
> +from lp.registry.
> from canonical.
> ITableBatchNavi
> from canonical.
> @@ -498,6 +498,14 @@
> "None if not a package branch."),
> schema=Interface, required=False, readonly=True))
>
> + is_personal_branch = exported(
> + Bool(
> + title=_("Personal Branch"), required=False,
> + readonly=True, default=False,
> + description=_(
> + "A branch is a personal branch if it is not associated "
> + "with a project nor a source package.")))
I guess a direct test for this would be nice, although it's so very
simple perhaps it's not needed...
> code_reviewer = Attribute( SourcePackages( ):
> "The reviewer if set, otherwise the owner of the branch.")
>
> @@ -925,6 +933,17 @@
> def associatedSuite
> """Return the suite source packages that this branch is linked to."""
>
> + def branchLinks():
> + """Return a sorted list of ICanHasLinkedBranch objects."""
> +
> + def branchIdentites():
Missing an 'i' here :)
> + """A list of aliases for a branch.
> +
> + Returns a list of tuples of bzr identity and context object. There is
> + at least one alias for any branch, and that is the branch itself. For
> + linked branches, the co...