Merge lp:~cjwatson/launchpad/git-target-inline-default-repo into lp:launchpad
- git-target-inline-default-repo
- Merge into devel
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 |
Related bugs: |
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-
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, |