Merge lp:~cjwatson/launchpad/git-ref-url into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: no longer in the source branch.
Merged at revision: 17404
Proposed branch: lp:~cjwatson/launchpad/git-ref-url
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/git-ref-scanner
Diff against target: 342 lines (+181/-3)
12 files modified
lib/lp/app/browser/configure.zcml (+8/-1)
lib/lp/app/browser/tales.py (+10/-1)
lib/lp/app/doc/tales.txt (+15/-0)
lib/lp/code/browser/configure.zcml (+18/-0)
lib/lp/code/browser/gitref.py (+19/-0)
lib/lp/code/browser/gitrepository.py (+22/-0)
lib/lp/code/browser/tests/test_gitrepository.py (+21/-0)
lib/lp/code/interfaces/gitref.py (+4/-0)
lib/lp/code/model/gitref.py (+4/-0)
lib/lp/code/model/tests/test_gitref.py (+24/-0)
lib/lp/code/templates/gitref-index.pt (+34/-0)
lib/lp/registry/browser/person.py (+2/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/git-ref-url
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+252900@code.launchpad.net

Commit message

Add basic navigation support for Git references.

Description of the change

Add basic navigation support for Git references.

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/app/browser/configure.zcml'
2--- lib/lp/app/browser/configure.zcml 2014-11-29 01:33:59 +0000
3+++ lib/lp/app/browser/configure.zcml 2015-03-13 14:20:26 +0000
4@@ -1,4 +1,4 @@
5-<!-- Copyright 2009-2014 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
10@@ -729,6 +729,13 @@
11 />
12
13 <adapter
14+ for="lp.code.interfaces.gitrepository.IGitRepository"
15+ provides="zope.traversing.interfaces.IPathAdapter"
16+ factory="lp.app.browser.tales.GitRepositoryFormatterAPI"
17+ name="fmt"
18+ />
19+
20+ <adapter
21 for="lp.bugs.interfaces.bugbranch.IBugBranch"
22 provides="zope.traversing.interfaces.IPathAdapter"
23 factory="lp.app.browser.tales.BugBranchFormatterAPI"
24
25=== modified file 'lib/lp/app/browser/tales.py'
26--- lib/lp/app/browser/tales.py 2014-11-27 05:01:51 +0000
27+++ lib/lp/app/browser/tales.py 2015-03-13 14:20:26 +0000
28@@ -1,4 +1,4 @@
29-# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
30+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
31 # GNU Affero General Public License version 3 (see the file LICENSE).
32
33 """Implementation of the lp: htmlform: fmt: namespaces in TALES."""
34@@ -1674,6 +1674,15 @@
35 }
36
37
38+class GitRepositoryFormatterAPI(CustomizableFormatter):
39+ """Adapter for IGitRepository objects to a formatted string."""
40+
41+ _link_summary_template = '%(display_name)s'
42+
43+ def _link_summary_values(self):
44+ return {'display_name': self._context.display_name}
45+
46+
47 class BugBranchFormatterAPI(CustomizableFormatter):
48 """Adapter providing fmt support for BugBranch objects"""
49
50
51=== modified file 'lib/lp/app/doc/tales.txt'
52--- lib/lp/app/doc/tales.txt 2014-12-31 13:24:43 +0000
53+++ lib/lp/app/doc/tales.txt 2015-03-13 14:20:26 +0000
54@@ -394,6 +394,7 @@
55
56 * people / teams
57 * branches
58+ * Git repositories
59 * bugs
60 * bug subscriptions
61 * bug tasks
62@@ -520,6 +521,20 @@
63 <a href=".../~eric/fooix/bar" class="sprite branch">lp://dev/fooix</a>
64
65
66+Git repositories
67+................
68+
69+For Git repositories, fmt:link links to the branch page.
70+
71+ >>> from lp.code.interfaces.gitrepository import GIT_FEATURE_FLAG
72+ >>> from lp.services.features.testing import FeatureFixture
73+ >>> with FeatureFixture({GIT_FEATURE_FLAG: 'on'}):
74+ ... repository = factory.makeGitRepository(
75+ ... owner=eric, target=fooix, name=u'bar')
76+ ... print test_tales("repository/fmt:link", repository=repository)
77+ <a href=".../~eric/fooix/+git/bar">lp:~eric/fooix/+git/bar</a>
78+
79+
80 Bugs
81 ....
82
83
84=== modified file 'lib/lp/code/browser/configure.zcml'
85--- lib/lp/code/browser/configure.zcml 2015-03-05 16:23:26 +0000
86+++ lib/lp/code/browser/configure.zcml 2015-03-13 14:20:26 +0000
87@@ -771,6 +771,9 @@
88 <browser:url
89 for="lp.code.interfaces.gitrepository.IGitRepository"
90 urldata="lp.code.browser.gitrepository.GitRepositoryURL"/>
91+ <browser:navigation
92+ module="lp.code.browser.gitrepository"
93+ classes="GitRepositoryNavigation"/>
94 <browser:menus
95 module="lp.code.browser.gitrepository"
96 classes="
97@@ -795,6 +798,21 @@
98 factory="lp.code.browser.gitrepository.GitRepositoryBreadcrumb"
99 permission="zope.Public"/>
100
101+ <browser:defaultView
102+ for="lp.code.interfaces.gitref.IGitRef"
103+ name="+index"/>
104+ <browser:url
105+ for="lp.code.interfaces.gitref.IGitRef"
106+ path_expression="string:+ref/${path}"
107+ attribute_to_parent="repository"
108+ rootsite="code"/>
109+ <browser:page
110+ for="lp.code.interfaces.gitref.IGitRef"
111+ class="lp.code.browser.gitref.GitRefView"
112+ permission="launchpad.View"
113+ name="+index"
114+ template="../templates/gitref-index.pt"/>
115+
116 <browser:menus
117 classes="ProductBranchesMenu"
118 module="lp.code.browser.branchlisting"/>
119
120=== added file 'lib/lp/code/browser/gitref.py'
121--- lib/lp/code/browser/gitref.py 1970-01-01 00:00:00 +0000
122+++ lib/lp/code/browser/gitref.py 2015-03-13 14:20:26 +0000
123@@ -0,0 +1,19 @@
124+# Copyright 2015 Canonical Ltd. This software is licensed under the
125+# GNU Affero General Public License version 3 (see the file LICENSE).
126+
127+"""Git reference views."""
128+
129+__metaclass__ = type
130+
131+__all__ = [
132+ 'GitRefView',
133+ ]
134+
135+from lp.services.webapp import LaunchpadView
136+
137+
138+class GitRefView(LaunchpadView):
139+
140+ @property
141+ def label(self):
142+ return self.context.display_name
143
144=== modified file 'lib/lp/code/browser/gitrepository.py'
145--- lib/lp/code/browser/gitrepository.py 2015-03-04 16:49:42 +0000
146+++ lib/lp/code/browser/gitrepository.py 2015-03-13 14:20:26 +0000
147@@ -8,6 +8,7 @@
148 __all__ = [
149 'GitRepositoryBreadcrumb',
150 'GitRepositoryContextMenu',
151+ 'GitRepositoryNavigation',
152 'GitRepositoryURL',
153 'GitRepositoryView',
154 ]
155@@ -16,12 +17,15 @@
156 from zope.interface import implements
157
158 from lp.app.browser.informationtype import InformationTypePortletMixin
159+from lp.app.errors import NotFoundError
160 from lp.code.interfaces.gitrepository import IGitRepository
161 from lp.services.config import config
162 from lp.services.webapp import (
163 ContextMenu,
164 LaunchpadView,
165 Link,
166+ Navigation,
167+ stepto,
168 )
169 from lp.services.webapp.authorization import (
170 check_permission,
171@@ -54,6 +58,24 @@
172 return self.context.unique_name.split("/")[-1]
173
174
175+class GitRepositoryNavigation(Navigation):
176+
177+ usedfor = IGitRepository
178+
179+ @stepto("+ref")
180+ def traverse_ref(self):
181+ segments = list(self.request.getTraversalStack())
182+ ref_segments = []
183+ while segments:
184+ ref_segments.append(segments.pop())
185+ ref = self.context.getRefByPath("/".join(ref_segments))
186+ if ref is not None:
187+ for _ in range(len(ref_segments)):
188+ self.request.stepstogo.consume()
189+ return ref
190+ raise NotFoundError
191+
192+
193 class GitRepositoryContextMenu(ContextMenu):
194 """Context menu for `IGitRepository`."""
195
196
197=== modified file 'lib/lp/code/browser/tests/test_gitrepository.py'
198--- lib/lp/code/browser/tests/test_gitrepository.py 2015-03-05 15:28:11 +0000
199+++ lib/lp/code/browser/tests/test_gitrepository.py 2015-03-13 14:20:26 +0000
200@@ -24,15 +24,36 @@
201 login_person,
202 logout,
203 person_logged_in,
204+ TestCaseWithFactory,
205 )
206 from lp.testing.layers import DatabaseFunctionalLayer
207 from lp.testing.pages import (
208 setupBrowser,
209 setupBrowserForUser,
210 )
211+from lp.testing.publication import test_traverse
212 from lp.testing.views import create_initialized_view
213
214
215+class TestGitRepositoryNavigation(TestCaseWithFactory):
216+
217+ layer = DatabaseFunctionalLayer
218+
219+ def setUp(self):
220+ super(TestGitRepositoryNavigation, self).setUp()
221+ self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
222+
223+ def test_traverse_ref(self):
224+ [ref] = self.factory.makeGitRefs()
225+ url = "%s/+ref/%s" % (canonical_url(ref.repository), ref.path)
226+ self.assertEqual(ref, test_traverse(url)[0])
227+
228+ def test_traverse_ref_missing(self):
229+ repository = self.factory.makeGitRepository()
230+ url = "%s/+ref/refs/heads/master" % canonical_url(repository)
231+ self.assertRaises(NotFound, test_traverse, url)
232+
233+
234 class TestGitRepositoryView(BrowserTestCase):
235
236 layer = DatabaseFunctionalLayer
237
238=== modified file 'lib/lp/code/interfaces/gitref.py'
239--- lib/lp/code/interfaces/gitref.py 2015-03-13 14:20:26 +0000
240+++ lib/lp/code/interfaces/gitref.py 2015-03-13 14:20:26 +0000
241@@ -41,3 +41,7 @@
242 object_type = Choice(
243 title=_("Object type"), required=True, readonly=True,
244 vocabulary=GitObjectType)
245+
246+ display_name = TextLine(
247+ title=_("Display name"), required=True, readonly=True,
248+ description=_("Display name of the reference."))
249
250=== modified file 'lib/lp/code/model/gitref.py'
251--- lib/lp/code/model/gitref.py 2015-03-13 14:20:26 +0000
252+++ lib/lp/code/model/gitref.py 2015-03-13 14:20:26 +0000
253@@ -35,3 +35,7 @@
254 commit_sha1 = Unicode(name='commit_sha1', allow_none=False)
255
256 object_type = EnumCol(enum=GitObjectType, notNull=True)
257+
258+ @property
259+ def display_name(self):
260+ return self.path.split("/", 2)[-1]
261
262=== added file 'lib/lp/code/model/tests/test_gitref.py'
263--- lib/lp/code/model/tests/test_gitref.py 1970-01-01 00:00:00 +0000
264+++ lib/lp/code/model/tests/test_gitref.py 2015-03-13 14:20:26 +0000
265@@ -0,0 +1,24 @@
266+# Copyright 2015 Canonical Ltd. This software is licensed under the
267+# GNU Affero General Public License version 3 (see the file LICENSE).
268+
269+"""Tests for Git references."""
270+
271+__metaclass__ = type
272+
273+from lp.code.interfaces.gitrepository import GIT_FEATURE_FLAG
274+from lp.services.features.testing import FeatureFixture
275+from lp.testing import TestCaseWithFactory
276+from lp.testing.layers import DatabaseFunctionalLayer
277+
278+
279+class TestGitRef(TestCaseWithFactory):
280+
281+ layer = DatabaseFunctionalLayer
282+
283+ def test_display_name(self):
284+ self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
285+ [master, personal] = self.factory.makeGitRefs(
286+ paths=[u"refs/heads/master", u"refs/heads/people/foo/bar"])
287+ self.assertEqual(
288+ ["master", "people/foo/bar"],
289+ [ref.display_name for ref in (master, personal)])
290
291=== added file 'lib/lp/code/templates/gitref-index.pt'
292--- lib/lp/code/templates/gitref-index.pt 1970-01-01 00:00:00 +0000
293+++ lib/lp/code/templates/gitref-index.pt 2015-03-13 14:20:26 +0000
294@@ -0,0 +1,34 @@
295+<html
296+ xmlns="http://www.w3.org/1999/xhtml"
297+ xmlns:tal="http://xml.zope.org/namespaces/tal"
298+ xmlns:metal="http://xml.zope.org/namespaces/metal"
299+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
300+ metal:use-macro="view/macro:page/main_side"
301+ i18n:domain="launchpad"
302+>
303+
304+<body>
305+
306+<div metal:fill-slot="main">
307+
308+ <div class="yui-g">
309+ <div id="ref-info" class="portlet">
310+ <h2>Branch information</h2>
311+ <div class="two-column-list">
312+ <dl id="name">
313+ <dt>Name:</dt>
314+ <dd tal:content="context/display_name" />
315+ </dl>
316+
317+ <dl id="repository">
318+ <dt>Repository:</dt>
319+ <dd tal:content="structure context/repository/fmt:link" />
320+ </dl>
321+ </div>
322+ </div>
323+ </div>
324+
325+</div>
326+
327+</body>
328+</html>
329
330=== modified file 'lib/lp/registry/browser/person.py'
331--- lib/lp/registry/browser/person.py 2015-03-09 15:04:54 +0000
332+++ lib/lp/registry/browser/person.py 2015-03-13 14:20:26 +0000
333@@ -379,7 +379,8 @@
334 iter_segments, owner=self.context)
335 if repository is None:
336 raise NotFoundError
337- for i in range(num_segments - len(list(iter_segments))):
338+ # Subtract one because the pillar has already been traversed.
339+ for _ in range(num_segments - len(list(iter_segments)) - 1):
340 self.request.stepstogo.consume()
341
342 if IProduct.providedBy(target):