Merge lp:~cjwatson/launchpad/git-target-inline-default-repo into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 17484
Proposed branch: lp:~cjwatson/launchpad/git-target-inline-default-repo
Merge into: lp:launchpad
Diff against target: 635 lines (+231/-86)
15 files modified
lib/lp/code/browser/branchlisting.py (+24/-1)
lib/lp/code/browser/gitrepository.py (+9/-18)
lib/lp/code/browser/tests/test_gitrepository.py (+0/-45)
lib/lp/code/browser/tests/test_product.py (+39/-1)
lib/lp/code/interfaces/gitrepository.py (+6/-0)
lib/lp/code/model/gitrepository.py (+15/-0)
lib/lp/code/model/tests/test_gitrepository.py (+41/-7)
lib/lp/code/templates/gitref-listing.pt (+1/-1)
lib/lp/code/templates/gitrepository-management.pt (+4/-4)
lib/lp/code/templates/product-branch-summary.pt (+20/-2)
lib/lp/code/templates/product-branches.pt (+14/-1)
lib/lp/registry/doc/product.txt (+25/-0)
lib/lp/registry/model/product.py (+12/-3)
lib/lp/registry/templates/productseries-setbranch.pt (+1/-1)
lib/lp/registry/tests/test_service_usage.py (+20/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/git-target-inline-default-repo
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+258033@code.launchpad.net

Commit message

If a project has a default Git repository, show its branches on the project code page.

Description of the change

If a project has a default Git repository, show its branches on the project code page.

This is a rushed job. We need to do something much smarter, perhaps having a mode switch to let a project say which it prefers, and the product-branch-summary handling is at best rough. But it makes the repository at least be visible somehow, while it was previously hard to find.

I haven't done anything for packages yet; that will come a little later.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/branchlisting.py'
2--- lib/lp/code/browser/branchlisting.py 2015-04-22 12:03:05 +0000
3+++ lib/lp/code/browser/branchlisting.py 2015-05-07 11:39:24 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Base class view for branch listings."""
10@@ -69,6 +69,7 @@
11 from lp.bugs.interfaces.bugbranch import IBugBranchSet
12 from lp.code.browser.branch import BranchMirrorMixin
13 from lp.code.browser.branchmergeproposallisting import ActiveReviewsView
14+from lp.code.browser.gitrepository import GitRefBatchNavigator
15 from lp.code.browser.summary import BranchCountSummaryView
16 from lp.code.enums import (
17 BranchLifecycleStatus,
18@@ -85,6 +86,7 @@
19 from lp.code.interfaces.branchcollection import IAllBranches
20 from lp.code.interfaces.branchnamespace import IBranchNamespacePolicy
21 from lp.code.interfaces.branchtarget import IBranchTarget
22+from lp.code.interfaces.gitrepository import IGitRepositorySet
23 from lp.code.interfaces.revision import IRevisionSet
24 from lp.code.interfaces.revisioncache import IRevisionCache
25 from lp.code.interfaces.seriessourcepackagebranch import (
26@@ -525,6 +527,7 @@
27 field_names = ['lifecycle', 'sort_by']
28 development_focus_branch = None
29 show_set_development_focus = False
30+ default_git_repository = None
31 custom_widget('lifecycle', LaunchpadDropdownWidget)
32 custom_widget('sort_by', LaunchpadDropdownWidget)
33 # Showing the series links is only really useful on product listing
34@@ -1096,6 +1099,26 @@
35 else:
36 return None
37
38+ @cachedproperty
39+ def default_git_repository(self):
40+ repository = getUtility(IGitRepositorySet).getDefaultRepository(
41+ self.context)
42+ if repository is None:
43+ return None
44+ elif check_permission('launchpad.View', repository):
45+ return repository
46+ else:
47+ return None
48+
49+ def default_git_repository_branches(self):
50+ """All branches in the default Git repository, sorted for display."""
51+ return GitRefBatchNavigator(self, self.default_git_repository)
52+
53+ @property
54+ def has_default_git_repository(self):
55+ """Is there a default Git repository?"""
56+ return self.default_git_repository is not None
57+
58 @property
59 def no_branch_message(self):
60 if (self.selected_lifecycle_status is not None
61
62=== modified file 'lib/lp/code/browser/gitrepository.py'
63--- lib/lp/code/browser/gitrepository.py 2015-04-21 09:31:58 +0000
64+++ lib/lp/code/browser/gitrepository.py 2015-05-07 11:39:24 +0000
65@@ -6,6 +6,7 @@
66 __metaclass__ = type
67
68 __all__ = [
69+ 'GitRefBatchNavigator',
70 'GitRepositoryBreadcrumb',
71 'GitRepositoryContextMenu',
72 'GitRepositoryNavigation',
73@@ -13,7 +14,7 @@
74 'GitRepositoryView',
75 ]
76
77-from bzrlib import urlutils
78+from storm.expr import Desc
79 from zope.interface import implements
80
81 from lp.app.browser.informationtype import InformationTypePortletMixin
82@@ -115,13 +116,19 @@
83 implements(IGitRefBatchNavigator)
84
85 def __init__(self, view, context):
86+ self.context = context
87 super(GitRefBatchNavigator, self).__init__(
88- context.branches, view.request,
89+ self._branches, view.request,
90 size=config.launchpad.branchlisting_batch_size)
91 self.view = view
92 self.column_count = 3
93
94 @property
95+ def _branches(self):
96+ from lp.code.model.gitref import GitRef
97+ return self.context.branches.order_by(Desc(GitRef.committer_date))
98+
99+ @property
100 def table_class(self):
101 # XXX: MichaelHudson 2007-10-18 bug=153894: This means there are two
102 # ways of sorting a one-page branch listing, which is confusing and
103@@ -151,22 +158,6 @@
104 self.request, "launchpad.LimitedView", authorised_people)
105
106 @property
107- def anon_url(self):
108- if self.context.visibleByUser(None):
109- return urlutils.join(
110- config.codehosting.git_anon_root, self.context.shortened_path)
111- else:
112- return None
113-
114- @property
115- def ssh_url(self):
116- if self.user is not None:
117- return urlutils.join(
118- config.codehosting.git_ssh_root, self.context.shortened_path)
119- else:
120- return None
121-
122- @property
123 def user_can_push(self):
124 """Whether the user can push to this branch."""
125 return check_permission("launchpad.Edit", self.context)
126
127=== modified file 'lib/lp/code/browser/tests/test_gitrepository.py'
128--- lib/lp/code/browser/tests/test_gitrepository.py 2015-03-24 15:15:23 +0000
129+++ lib/lp/code/browser/tests/test_gitrepository.py 2015-05-07 11:39:24 +0000
130@@ -8,7 +8,6 @@
131 from datetime import datetime
132
133 from BeautifulSoup import BeautifulSoup
134-from bzrlib import urlutils
135 from fixtures import FakeLogger
136 import pytz
137 from testtools.matchers import Equals
138@@ -21,7 +20,6 @@
139 from lp.code.interfaces.gitrepository import GIT_FEATURE_FLAG
140 from lp.code.interfaces.revision import IRevisionSet
141 from lp.registry.interfaces.person import PersonVisibility
142-from lp.services.config import config
143 from lp.services.features.testing import FeatureFixture
144 from lp.services.webapp.publisher import canonical_url
145 from lp.testing import (
146@@ -70,49 +68,6 @@
147 super(TestGitRepositoryView, self).setUp()
148 self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
149
150- def test_anon_url_for_public(self):
151- # Public repositories have an anonymous URL, visible to anyone.
152- repository = self.factory.makeGitRepository()
153- view = create_initialized_view(repository, "+index")
154- expected_url = urlutils.join(
155- config.codehosting.git_anon_root, repository.shortened_path)
156- self.assertEqual(expected_url, view.anon_url)
157-
158- def test_anon_url_not_for_private(self):
159- # Private repositories do not have an anonymous URL.
160- owner = self.factory.makePerson()
161- repository = self.factory.makeGitRepository(
162- owner=owner, information_type=InformationType.USERDATA)
163- with person_logged_in(owner):
164- view = create_initialized_view(repository, "+index")
165- self.assertIsNone(view.anon_url)
166-
167- def test_ssh_url_for_public_logged_in(self):
168- # Public repositories have an SSH URL, visible if logged in.
169- repository = self.factory.makeGitRepository()
170- with person_logged_in(repository.owner):
171- view = create_initialized_view(repository, "+index")
172- expected_url = urlutils.join(
173- config.codehosting.git_ssh_root, repository.shortened_path)
174- self.assertEqual(expected_url, view.ssh_url)
175-
176- def test_ssh_url_for_public_not_anonymous(self):
177- # Public repositories do not have an SSH URL if not logged in.
178- repository = self.factory.makeGitRepository()
179- view = create_initialized_view(repository, "+index")
180- self.assertIsNone(view.ssh_url)
181-
182- def test_ssh_url_for_private(self):
183- # Private repositories have an SSH URL.
184- owner = self.factory.makePerson()
185- repository = self.factory.makeGitRepository(
186- owner=owner, information_type=InformationType.USERDATA)
187- with person_logged_in(owner):
188- view = create_initialized_view(repository, "+index")
189- expected_url = urlutils.join(
190- config.codehosting.git_ssh_root, repository.shortened_path)
191- self.assertEqual(expected_url, view.ssh_url)
192-
193 def test_user_can_push(self):
194 # A user can push if they have edit permissions.
195 repository = self.factory.makeGitRepository()
196
197=== modified file 'lib/lp/code/browser/tests/test_product.py'
198--- lib/lp/code/browser/tests/test_product.py 2014-02-25 06:42:01 +0000
199+++ lib/lp/code/browser/tests/test_product.py 2015-05-07 11:39:24 +0000
200@@ -1,4 +1,4 @@
201-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
202+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
203 # GNU Affero General Public License version 3 (see the file LICENSE).
204
205 """Tests for the product view classes and templates."""
206@@ -19,8 +19,13 @@
207 ServiceUsage,
208 )
209 from lp.code.enums import BranchType
210+from lp.code.interfaces.gitrepository import (
211+ GIT_FEATURE_FLAG,
212+ IGitRepositorySet,
213+ )
214 from lp.code.interfaces.revision import IRevisionSet
215 from lp.registry.enums import BranchSharingPolicy
216+from lp.services.features.testing import FeatureFixture
217 from lp.services.webapp import canonical_url
218 from lp.testing import (
219 ANONYMOUS,
220@@ -207,6 +212,39 @@
221 expected = 'There are no branches for %s' % product.displayname
222 self.assertIn(expected, html)
223
224+ def test_no_default_git_repository(self):
225+ # If there is no default Git repository, Product:+branches does not
226+ # try to render one.
227+ product = self.factory.makeProduct()
228+ view = create_initialized_view(
229+ product, '+branches', rootsite='code', principal=product.owner)
230+ self.assertIsNone(view.default_git_repository)
231+ self.assertFalse(view.has_default_git_repository)
232+ content = view()
233+ self.assertNotIn('git clone', content)
234+
235+ def test_default_git_repository(self):
236+ # If there is a default Git repository, Product:+branches shows a
237+ # summary of its branches.
238+ self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
239+ product = self.factory.makeProduct()
240+ repository = self.factory.makeGitRepository(target=product)
241+ self.factory.makeGitRefs(
242+ repository=repository,
243+ paths=[u"refs/heads/master", u"refs/heads/another-branch"])
244+ with person_logged_in(product.owner):
245+ getUtility(IGitRepositorySet).setDefaultRepository(
246+ product, repository)
247+ view = create_initialized_view(
248+ product, '+branches', rootsite='code', principal=product.owner)
249+ self.assertEqual(repository, view.default_git_repository)
250+ self.assertTrue(view.has_default_git_repository)
251+ content = view()
252+ self.assertIn('git clone', content)
253+ # XXX cjwatson 2015-04-30: These tests are not very precise.
254+ self.assertIn('master', content)
255+ self.assertIn('another-branch', content)
256+
257
258 class TestProductCodeIndexServiceUsages(ProductTestBase, BrowserTestCase):
259 """Tests for the product code page, especially the usage messasges."""
260
261=== modified file 'lib/lp/code/interfaces/gitrepository.py'
262--- lib/lp/code/interfaces/gitrepository.py 2015-05-06 15:01:10 +0000
263+++ lib/lp/code/interfaces/gitrepository.py 2015-05-07 11:39:24 +0000
264@@ -217,6 +217,12 @@
265 "The identity of this repository: a VCS-independent synonym for "
266 "git_identity.")
267
268+ anon_url = Attribute(
269+ "An anonymous (git://) URL for this repository, or None in the case "
270+ "of private repositories.")
271+
272+ ssh_url = Attribute("A git+ssh:// URL for this repository.")
273+
274 refs = exported(CollectionField(
275 title=_("The references present in this repository."),
276 readonly=True,
277
278=== modified file 'lib/lp/code/model/gitrepository.py'
279--- lib/lp/code/model/gitrepository.py 2015-05-06 15:01:10 +0000
280+++ lib/lp/code/model/gitrepository.py 2015-05-07 11:39:24 +0000
281@@ -322,6 +322,21 @@
282 config.codehosting.git_browse_root, self.unique_name)
283
284 @property
285+ def anon_url(self):
286+ """See `IGitRepository`."""
287+ if self.visibleByUser(None):
288+ return urlutils.join(
289+ config.codehosting.git_anon_root, self.shortened_path)
290+ else:
291+ return None
292+
293+ @property
294+ def ssh_url(self):
295+ """See `IGitRepository`."""
296+ return urlutils.join(
297+ config.codehosting.git_ssh_root, self.shortened_path)
298+
299+ @property
300 def private(self):
301 return self.information_type in PRIVATE_INFORMATION_TYPES
302
303
304=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
305--- lib/lp/code/model/tests/test_gitrepository.py 2015-05-06 15:01:10 +0000
306+++ lib/lp/code/model/tests/test_gitrepository.py 2015-05-07 11:39:24 +0000
307@@ -11,6 +11,7 @@
308 import hashlib
309 import json
310
311+from bzrlib import urlutils
312 from lazr.lifecycle.event import ObjectModifiedEvent
313 from lazr.lifecycle.snapshot import Snapshot
314 import transaction
315@@ -72,6 +73,7 @@
316 )
317 from lp.registry.interfaces.personproduct import IPersonProductFactory
318 from lp.registry.tests.test_accesspolicy import get_policies_for_artifact
319+from lp.services.config import config
320 from lp.services.database.constants import UTC_NOW
321 from lp.services.features.testing import FeatureFixture
322 from lp.services.mail import stub
323@@ -353,21 +355,53 @@
324 # actually notices any interesting kind of repository modifications.
325
326
327-class TestCodebrowse(TestCaseWithFactory):
328- """Tests for Git repository codebrowse support."""
329+class TestGitRepositoryURLs(TestCaseWithFactory):
330+ """Tests for Git repository URLs."""
331
332 layer = DatabaseFunctionalLayer
333
334 def setUp(self):
335- super(TestCodebrowse, self).setUp()
336+ super(TestGitRepositoryURLs, self).setUp()
337 self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
338
339- def test_simple(self):
340+ def test_codebrowse_url(self):
341 # The basic codebrowse URL for a repository is an 'https' URL.
342 repository = self.factory.makeGitRepository()
343- self.assertEqual(
344- "https://git.launchpad.dev/" + repository.unique_name,
345- repository.getCodebrowseUrl())
346+ expected_url = urlutils.join(
347+ config.codehosting.git_browse_root, repository.unique_name)
348+ self.assertEqual(expected_url, repository.getCodebrowseUrl())
349+
350+ def test_anon_url_for_public(self):
351+ # Public repositories have an anonymous URL, visible to anyone.
352+ repository = self.factory.makeGitRepository()
353+ expected_url = urlutils.join(
354+ config.codehosting.git_anon_root, repository.shortened_path)
355+ self.assertEqual(expected_url, repository.anon_url)
356+
357+ def test_anon_url_not_for_private(self):
358+ # Private repositories do not have an anonymous URL.
359+ owner = self.factory.makePerson()
360+ repository = self.factory.makeGitRepository(
361+ owner=owner, information_type=InformationType.USERDATA)
362+ with person_logged_in(owner):
363+ self.assertIsNone(repository.anon_url)
364+
365+ def test_ssh_url_for_public(self):
366+ # Public repositories have an SSH URL.
367+ repository = self.factory.makeGitRepository()
368+ expected_url = urlutils.join(
369+ config.codehosting.git_ssh_root, repository.shortened_path)
370+ self.assertEqual(expected_url, repository.ssh_url)
371+
372+ def test_ssh_url_for_private(self):
373+ # Private repositories have an SSH URL.
374+ owner = self.factory.makePerson()
375+ repository = self.factory.makeGitRepository(
376+ owner=owner, information_type=InformationType.USERDATA)
377+ with person_logged_in(owner):
378+ expected_url = urlutils.join(
379+ config.codehosting.git_ssh_root, repository.shortened_path)
380+ self.assertEqual(expected_url, repository.ssh_url)
381
382
383 class TestGitRepositoryNamespace(TestCaseWithFactory):
384
385=== modified file 'lib/lp/code/templates/gitref-listing.pt'
386--- lib/lp/code/templates/gitref-listing.pt 2015-04-29 15:06:39 +0000
387+++ lib/lp/code/templates/gitref-listing.pt 2015-05-07 11:39:24 +0000
388@@ -24,7 +24,7 @@
389 </div>
390 </tal:needs-batch>
391
392- <table tal:attributes="class context/table_class" id="branchtable">
393+ <table tal:attributes="class context/table_class" id="gitreftable">
394 <thead>
395 <tr>
396 <th>Name</th>
397
398=== modified file 'lib/lp/code/templates/gitrepository-management.pt'
399--- lib/lp/code/templates/gitrepository-management.pt 2015-03-04 16:49:42 +0000
400+++ lib/lp/code/templates/gitrepository-management.pt 2015-05-07 11:39:24 +0000
401@@ -7,15 +7,15 @@
402 <dl id="clone-url">
403 <dt>Get this repository:</dt>
404 <dd>
405- <tal:anonymous condition="view/anon_url">
406+ <tal:anonymous condition="context/anon_url">
407 <tt class="command">
408- git clone <span class="anon-url" tal:content="view/anon_url" />
409+ git clone <span class="anon-url" tal:content="context/anon_url" />
410 </tt>
411 <br />
412 </tal:anonymous>
413- <tal:ssh condition="view/ssh_url">
414+ <tal:ssh condition="view/user">
415 <tt class="command">
416- git clone <span class="ssh-url" tal:content="view/ssh_url" />
417+ git clone <span class="ssh-url" tal:content="context/ssh_url" />
418 </tt>
419 </tal:ssh>
420 </dd>
421
422=== modified file 'lib/lp/code/templates/product-branch-summary.pt'
423--- lib/lp/code/templates/product-branch-summary.pt 2012-10-08 02:02:19 +0000
424+++ lib/lp/code/templates/product-branch-summary.pt 2015-05-07 11:39:24 +0000
425@@ -56,7 +56,8 @@
426 </p>
427 </div>
428
429- <tal:no-branches condition="not: view/branch_count">
430+ <tal:no-branches
431+ condition="python: not view.branch_count and not view.has_default_git_repository">
432 There are no branches for <tal:project-name replace="context/displayname"/>
433 in Launchpad.
434 <tal:can-configure condition="view/can_configure_branches">
435@@ -78,6 +79,23 @@
436 </tal:can-configure>
437 </tal:no-branches>
438
439+ <div tal:condition="view/has_default_git_repository"
440+ style="margin: 1em 0"
441+ tal:define="repository view/default_git_repository">
442+ You can
443+ <a tal:attributes="href repository/getCodebrowseUrl">browse the
444+ source code</a>
445+ for the default Git repository or get a copy of the repository using
446+ the command:<br/>
447+ <tt class="command">git clone
448+ <tal:logged-in condition="view/user">
449+ <tal:git-ssh-url replace="repository/ssh_url"/>
450+ </tal:logged-in>
451+ <tal:not-logged-in condition="not: view/user">
452+ <tal:git-anon-url replace="repository/anon_url"/>
453+ </tal:not-logged-in></tt>
454+ </div>
455+
456 <tal:has-branches condition="view/branch_count">
457 <div tal:condition="view/has_development_focus_branch"
458 style="margin: 1em 0"
459@@ -99,7 +117,7 @@
460 <tal:has-user condition="view/user">
461 <p id="push-instructions"
462 tal:condition="not: context/codehosting_usage/enumvalue:UNKNOWN">
463- You can push the branch directly to Launchpad with the command:<br />
464+ You can push a Bazaar branch directly to Launchpad with the command:<br />
465 <tt class="command">
466 bzr push lp:~<tal:user replace="view/user/name"/>/<tal:project replace="context/name"/>/<tal:series replace="context/name"/>
467 </tt>
468
469=== modified file 'lib/lp/code/templates/product-branches.pt'
470--- lib/lp/code/templates/product-branches.pt 2012-10-08 02:02:19 +0000
471+++ lib/lp/code/templates/product-branches.pt 2015-05-07 11:39:24 +0000
472@@ -60,9 +60,22 @@
473 condition="not: view/context/codehosting_usage/enumvalue:UNKNOWN"
474 replace="structure context/@@+portlet-product-codestatistics" />
475
476+ <tal:has-default-git-repository condition="view/has_default_git_repository">
477+ <div id="default-repository-branches" class="portlet"
478+ tal:define="repository view/default_git_repository;
479+ branches view/default_git_repository_branches">
480+ <h2>Git branches</h2>
481+ <tal:default-repository-branches
482+ replace="structure branches/@@+ref-listing" />
483+ </div>
484+ </tal:has-default-git-repository>
485+
486 <tal:has-branches condition="view/branch_count"
487 define="branches view/branches">
488- <tal:branchlisting content="structure branches/@@+branch-listing" />
489+ <div class="portlet">
490+ <h2>Bazaar branches</h2>
491+ <tal:branchlisting content="structure branches/@@+branch-listing" />
492+ </div>
493 </tal:has-branches>
494
495 </tal:main>
496
497=== modified file 'lib/lp/registry/doc/product.txt'
498--- lib/lp/registry/doc/product.txt 2015-01-29 18:43:52 +0000
499+++ lib/lp/registry/doc/product.txt 2015-05-07 11:39:24 +0000
500@@ -521,6 +521,31 @@
501 landscape
502
503
504+Products with Git repositories
505+------------------------------
506+
507+Products are considered to officially support Launchpad as a location for
508+their code if they have a default Git repository.
509+
510+ >>> from lp.code.interfaces.gitrepository import (
511+ ... GIT_FEATURE_FLAG,
512+ ... IGitRepositorySet,
513+ ... )
514+ >>> from lp.services.features.testing import FeatureFixture
515+ >>> firefox.development_focus.branch = None
516+ >>> print firefox.official_codehosting
517+ False
518+ >>> print firefox.codehosting_usage.name
519+ UNKNOWN
520+ >>> with FeatureFixture({GIT_FEATURE_FLAG: 'on'}):
521+ ... getUtility(IGitRepositorySet).setDefaultRepository(
522+ ... firefox, factory.makeGitRepository(target=firefox))
523+ >>> print firefox.official_codehosting
524+ True
525+ >>> print firefox.codehosting_usage.name
526+ LAUNCHPAD
527+
528+
529 Primary translatable
530 --------------------
531
532
533=== modified file 'lib/lp/registry/model/product.py'
534--- lib/lp/registry/model/product.py 2015-03-17 10:45:07 +0000
535+++ lib/lp/registry/model/product.py 2015-05-07 11:39:24 +0000
536@@ -116,6 +116,7 @@
537 )
538 from lp.code.enums import BranchType
539 from lp.code.interfaces.branch import DEFAULT_BRANCH_STATUS_IN_LISTING
540+from lp.code.interfaces.gitrepository import IGitRepositorySet
541 from lp.code.model.branch import Branch
542 from lp.code.model.branchnamespace import BRANCH_POLICY_ALLOWED_TYPES
543 from lp.code.model.hasbranches import (
544@@ -581,7 +582,10 @@
545
546 @property
547 def official_codehosting(self):
548- return self.development_focus.branch is not None
549+ repository = getUtility(IGitRepositorySet).getDefaultRepository(self)
550+ return (
551+ self.development_focus.branch is not None or
552+ repository is not None)
553
554 @property
555 def official_anything(self):
556@@ -613,9 +617,14 @@
557
558 @property
559 def codehosting_usage(self):
560- if self.development_focus.branch is None:
561+ repository = getUtility(IGitRepositorySet).getDefaultRepository(self)
562+ if self.development_focus.branch is None and repository is None:
563 return ServiceUsage.UNKNOWN
564- elif self.development_focus.branch.branch_type == BranchType.HOSTED:
565+ elif (repository is not None or
566+ self.development_focus.branch.branch_type == BranchType.HOSTED):
567+ # XXX cjwatson 2015-07-07: Fix this when we have
568+ # GitRepositoryType; an imported default repository should imply
569+ # ServiceUsage.EXTERNAL.
570 return ServiceUsage.LAUNCHPAD
571 elif self.development_focus.branch.branch_type in (
572 BranchType.MIRRORED,
573
574=== modified file 'lib/lp/registry/templates/productseries-setbranch.pt'
575--- lib/lp/registry/templates/productseries-setbranch.pt 2012-10-09 01:07:52 +0000
576+++ lib/lp/registry/templates/productseries-setbranch.pt 2015-05-07 11:39:24 +0000
577@@ -19,7 +19,7 @@
578 <div metal:fill-slot="main">
579
580 <p id="push-instructions">
581- You can push the branch directly to Launchpad with the command:<br />
582+ You can push a Bazaar branch directly to Launchpad with the command:<br />
583 <tt class="command">
584 bzr push lp:~<tal:user replace="view/user/name"/>/<tal:project replace="context/product/name"/>/<tal:series replace="context/name"/>
585 </tt>
586
587=== modified file 'lib/lp/registry/tests/test_service_usage.py'
588--- lib/lp/registry/tests/test_service_usage.py 2012-01-01 02:58:52 +0000
589+++ lib/lp/registry/tests/test_service_usage.py 2015-05-07 11:39:24 +0000
590@@ -1,10 +1,17 @@
591-# Copyright 2010 Canonical Ltd. This software is licensed under the
592+# Copyright 2010-2015 Canonical Ltd. This software is licensed under the
593 # GNU Affero General Public License version 3 (see the file LICENSE).
594
595 __metaclass__ = type
596
597+from zope.component import getUtility
598+
599 from lp.app.enums import ServiceUsage
600 from lp.code.enums import BranchType
601+from lp.code.interfaces.gitrepository import (
602+ GIT_FEATURE_FLAG,
603+ IGitRepositorySet,
604+ )
605+from lp.services.features.testing import FeatureFixture
606 from lp.testing import (
607 login_person,
608 TestCaseWithFactory,
609@@ -192,7 +199,7 @@
610 self.target.codehosting_usage)
611
612 def test_codehosting_hosted_branch(self):
613- # A branch on Launchpad is HOSTED.
614+ # A branch on Launchpad has LAUNCHPAD usage.
615 login_person(self.target.owner)
616 self.target.development_focus.branch = self.factory.makeProductBranch(
617 product=self.target,
618@@ -201,6 +208,17 @@
619 ServiceUsage.LAUNCHPAD,
620 self.target.codehosting_usage)
621
622+ def test_codehosting_default_git_repository(self):
623+ # A default Git repository on Launchpad has LAUNCHPAD usage.
624+ self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
625+ login_person(self.target.owner)
626+ repository = self.factory.makeGitRepository(target=self.target)
627+ getUtility(IGitRepositorySet).setDefaultRepository(
628+ self.target, repository)
629+ self.assertEqual(
630+ ServiceUsage.LAUNCHPAD,
631+ self.target.codehosting_usage)
632+
633
634 class TestProductSeriesUsageEnums(
635 TestCaseWithFactory,