Merge lp:~thumper/launchpad/package-branch-listing-speedup into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Approved by: Michael Hudson-Doyle
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
Reviewer Review Type Date Requested Status
Michael Hudson-Doyle Approve
Review via email: mp+22993@code.launchpad.net

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_branchlisting.py
  TestBranchLinksAndIdentites
  TestBzrIdentity
  TestLinkedBranchSorting

qa:
  look at https://code.edge.launchpad.net/rhythmbox-ubuntuone-music-store
  and make sure it looks right

changes:

pre and mid impl calls with mwhudson

lib/lp/code/browser/branchlisting.py
 * 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 associatedProductSeries and
   associatedSuiteSourcePackages to be accessed to avoid database calls.
 * the is_dev_focus is no longer needed, so the methods to work it out are
   removed

lib/lp/code/browser/tests/test_branchlisting.py
 * adds a single test to make sure there are no extra queries executed
   to work out the bzr_identity of a BranchListingItem

lib/lp/code/configure.zcml
 * Makes the is_personal_branch, branchIdentities, and branchLinks
   methods available through IBranch

lib/lp/code/interfaces/branch.py
 * Removes the bazaar_identity
 * Adds the BzrIdentityMixin

lib/lp/code/interfaces/linkedbranch.py
 * Makes the context accessible from the object.

lib/lp/code/model/branch.py
 * 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/code/model/linkedbranch.py
 * Link objects are now sortable, so the most 'important' links appear first.

lib/lp/code/model/tests/test_branch.py
lib/lp/code/model/tests/test_linkedbranch.py
 * implement the tests mentioned above

lib/lp/testing/factory.py
 * There were many places where a string was being passed in for a
   sourcepackagename. This was then being passed around as a
   sourcepackagename object, which it isn't. These changes add the
   ability to pass in a string for the sourcepackagename, and the
   correct object will be created.

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :
Download full text (28.8 KiB)

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'
> --- lib/lp/code/interfaces/branch.py 2010-04-06 17:19:23 +0000
> +++ lib/lp/code/interfaces/branch.py 2010-04-08 04:14:00 +0000
> @@ -8,7 +8,6 @@
> __metaclass__ = type
>
> __all__ = [
> - 'bazaar_identity',
> 'BRANCH_NAME_VALIDATION_ERROR_MESSAGE',
> 'branch_name_validator',
> 'BranchCannotBePrivate',
> @@ -21,6 +20,7 @@
> 'BranchExists',
> 'BranchTargetError',
> 'BranchTypeError',
> + 'BzrIdentityMixin',
> 'CannotDeleteBranch',
> 'DEFAULT_BRANCH_STATUS_IN_LISTING',
> 'get_blacklisted_hostnames',
> @@ -32,12 +32,10 @@
> 'IBranchNavigationMenu',
> 'IBranchSet',
> 'NoSuchBranch',
> - 'UnknownBranchTypeError',
> 'user_has_special_branch_access',
> ]
>
> from cgi import escape
> -from operator import attrgetter
> import re
>
> from zope.component import getUtility
> @@ -69,12 +67,14 @@
> )
> from lp.code.interfaces.branchlookup import IBranchLookup
> from lp.code.interfaces.branchtarget import IHasBranchTarget
> +from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
> from lp.code.interfaces.hasbranches import IHasMergeProposals
> from lp.code.interfaces.hasrecipes import IHasRecipes
> from canonical.launchpad.interfaces.launchpad import (
> ILaunchpadCelebrities, IPrivacy)
> from lp.registry.interfaces.role import IHasOwner
> from lp.registry.interfaces.person import IPerson
> +from lp.registry.interfaces.pocket import PackagePublishingPocket
> from canonical.launchpad.webapp.interfaces import (
> ITableBatchNavigator, NameLookupFailed)
> from canonical.launchpad.webapp.menu import structured
> @@ -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(
> "The reviewer if set, otherwise the owner of the branch.")
>
> @@ -925,6 +933,17 @@
> def associatedSuiteSourcePackages():
> """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...

review: Approve
Revision history for this message
Tim Penhey (thumper) wrote :
Download full text (3.7 KiB)

> 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/code/model/linkedbranch.py'
> > + # 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/code/model/tests/test_branch.py'
> > +class TestBranchLinksAndIdentites(TestCaseWithFactory):
> > + """Test IBranch.branchLinks and IBranch.branchIdentities."""
> > +
> > + layer = DatabaseFunctionalLayer
> > +
> > + def test_default_identities(self):
> > + # 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_to_product_series(self):
> > + # 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_to_package_not_current_series(self):
> > + # 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(TestCaseWithFactory):
> > """Test IBranch.bzr_identity."""
>
> You don't test here any branches that are linked to both products and
> sou...

Read more...

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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:&lt;script id='branch-sparks' type='text/javascript'&gt;" />
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:&lt;/script&gt;" />
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