Merge lp:~thomir-deactivatedaccount/launchpad/devel-fix-git-links into lp:launchpad

Proposed by Thomi Richards
Status: Merged
Merged at revision: 18115
Proposed branch: lp:~thomir-deactivatedaccount/launchpad/devel-fix-git-links
Merge into: lp:launchpad
Diff against target: 600 lines (+366/-32)
11 files modified
lib/lp/app/browser/configure.zcml (+1/-1)
lib/lp/app/browser/launchpad.py (+66/-1)
lib/lp/app/browser/linkchecker.py (+13/-10)
lib/lp/app/browser/stringformatter.py (+1/-1)
lib/lp/app/browser/tests/test_launchpad.py (+251/-1)
lib/lp/app/browser/tests/test_linkchecker.py (+14/-1)
lib/lp/app/doc/displaying-paragraphs-of-text.txt (+11/-11)
lib/lp/app/javascript/lp-links.js (+1/-2)
lib/lp/app/javascript/tests/test_lp_links.html (+2/-2)
lib/lp/app/javascript/tests/test_lp_links.js (+2/-2)
lib/lp/app/stories/basics/notfound-traversals.txt (+4/-0)
To merge this branch: bzr merge lp:~thomir-deactivatedaccount/launchpad/devel-fix-git-links
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+297963@code.launchpad.net

Commit message

Links to git repositories are now linked correctly.

Description of the change

Make links to git repositories work in launchpad.

There are several changes here:

 * Links to bzr branches or git repos in the form lp:<path> now generate expanded links in the form: /+code/<path> instead of /+branch/<path>. The +branch form still exists, as it's used by bzr clients, but it only deals with bazaar branches.

 * The link checker API method now deals with +code links, instead of +branch links.

 * +code links now traverse correctly to git repositories or bzr branches.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

I won't mention it at each site, but please sort imports. utilities/format-new-and-modified-imports can help with this (though it's worth double-checking what it does). Note also that imports are normally sorted case-insensitively in LP, even though I personally find that a bit weird.

I think some more tests should be updated:

lib/lp/app/doc/displaying-paragraphs-of-text.txt
lib/lp/app/javascript/tests/test_lp_links.*

... and the comment in lib/lp/app/javascript/lp-links.js should probably be updated too.

review: Needs Fixing
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

Thanks for the review - I've made the changes requested.

I also have a few questions:

I notice we have a few instances of "+branch" in blueprints - I'm not sure what for, or if they can be trivially updated to "+code" - it seemed like it would be more than a simple s/branch/code/ operation...

Should lp.code.browser.tests.test_branchtraversal be updated to use +code instead of +branch?

There are various other instances of '+branch' in the codebase, and I'm not sure which of them, if any need updating.

Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

Ugh - there's a number of failing tests in doctests. I need to spend some time figuring out how to fix them. In the meantime, this isn't ready for your review.

Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

Tests are fixed now. William helped point me at the problem & solution.

Ready for a review, if you have the time.

Revision history for this message
Colin Watson (cjwatson) wrote :

The instances of +branch in blueprints are traversals under Specification, which shouldn't be changed here. (Linking directly to Git branches there wouldn't be appropriate, but we may at some point allow them to be linked to merge proposals, much like the similar work I'm doing for bugs.)

I think the traversal tests in lp.code.browser.tests.test_branchtraversal are under Person rather than under the root, so they also shouldn't be changed.

I'm happy with most of this now, but there is one oddity. Revision 18107 in your branch seems to have partially reverted some previous fixes you applied in response to my previous review feedback, so e.g. it still has the dubious check for '+git' as a path substring. Was there a particular reason for that, or was it an accident? If that was in response to some test failures (query counts maybe?), I think we need to find a different fix to those. So I'm casting an Approve review in order not to block you, but please do fix that up before landing.

review: Approve
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

> I'm happy with most of this now, but there is one oddity. Revision 18107 in
> your branch seems to have partially reverted some previous fixes you applied
> in response to my previous review feedback, so e.g. it still has the dubious
> check for '+git' as a path substring. Was there a particular reason for that,
> or was it an accident?

Ugh - that was an accident. Reverting now.

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 2016-05-12 14:08:38 +0000
3+++ lib/lp/app/browser/configure.zcml 2016-06-22 21:05:27 +0000
4@@ -1118,7 +1118,7 @@
5 <!-- virtual host: code -->
6 <browser:defaultView
7 for="lp.services.webapp.interfaces.ILaunchpadRoot"
8- name="+code"
9+ name="+code-index"
10 layer="lp.code.publisher.CodeLayer"
11 />
12 <browser:page
13
14=== modified file 'lib/lp/app/browser/launchpad.py'
15--- lib/lp/app/browser/launchpad.py 2016-05-10 10:53:32 +0000
16+++ lib/lp/app/browser/launchpad.py 2016-06-22 21:05:27 +0000
17@@ -78,6 +78,7 @@
18 )
19 from lp.app.errors import (
20 GoneError,
21+ NameLookupFailed,
22 NotFoundError,
23 POSTToNonCanonicalURL,
24 )
25@@ -103,9 +104,11 @@
26 from lp.code.interfaces.branchlookup import IBranchLookup
27 from lp.code.interfaces.codehosting import IBazaarApplication
28 from lp.code.interfaces.codeimport import ICodeImportSet
29+from lp.code.interfaces.gitlookup import IGitLookup
30 from lp.code.interfaces.gitrepository import IGitRepositorySet
31 from lp.hardwaredb.interfaces.hwdb import IHWDBApplication
32 from lp.layers import WebServiceLayer
33+from lp.registry.enums import VCSType
34 from lp.registry.interfaces.announcement import IAnnouncementSet
35 from lp.registry.interfaces.codeofconduct import ICodeOfConductSet
36 from lp.registry.interfaces.distribution import IDistributionSet
37@@ -767,6 +770,68 @@
38
39 return self.redirectSubTree(target_url)
40
41+ @stepto('+code')
42+ def redirect_branch_or_repo(self):
43+ """Redirect /+code/<foo> to the branch or repository named 'foo'.
44+
45+ 'foo' can be the unique name of the branch/repo, or any of the aliases
46+ for the branch/repo.
47+
48+ If 'foo' is invalid, or exists but the user does not have permission to
49+ view 'foo', a NotFoundError is raised, resulting in a 404 error.
50+
51+ Unlike +branch, this works for both git and bzr repositories/branches.
52+ """
53+ target_url = None
54+ path = '/'.join(self.request.stepstogo)
55+
56+ # Try a Git repository lookup first, since the schema is simpler and
57+ # so it's quicker.
58+ try:
59+ repository, trailing = getUtility(IGitLookup).getByPath(path)
60+ if repository is not None:
61+ target_url = canonical_url(repository)
62+ if trailing:
63+ target_url = urlappend(target_url, trailing)
64+ except (InvalidNamespace, InvalidProductName, NameLookupFailed,
65+ Unauthorized):
66+ # Either the git repository wasn't found, or it was found but we
67+ # lack authority to access it. In either case, attempt a bzr lookup
68+ # so don't set target_url
69+ pass
70+
71+ # Attempt a bzr lookup as well:
72+ try:
73+ branch, trailing = getUtility(IBranchLookup).getByLPPath(path)
74+ bzr_url = canonical_url(branch)
75+ if trailing != '':
76+ bzr_url = urlappend(bzr_url, trailing)
77+
78+ if target_url and branch.product is not None:
79+ # Project has both a bzr branch and a git repo. There's no
80+ # right thing we can do here, so pretend we didn't see
81+ # anything at all.
82+ if branch.product.vcs is None:
83+ target_url = None
84+ # if it's set to BZR, then set this branch as the target
85+ if branch.product.vcs == VCSType.BZR:
86+ target_url = bzr_url
87+ else:
88+ target_url = bzr_url
89+ except (NoLinkedBranch, CannotHaveLinkedBranch, InvalidNamespace,
90+ InvalidProductName, NotFoundError):
91+ # No bzr branch found either.
92+ pass
93+
94+ # Either neither bzr nor git returned matches, or they did but we're
95+ # not authorised to view them, or they both did and the project has not
96+ # set its 'vcs' property to indicate which one to prefer. In all cases
97+ # raise a 404:
98+ if not target_url:
99+ raise NotFoundError
100+
101+ return self.redirectSubTree(target_url)
102+
103 @stepto('+builds')
104 def redirect_buildfarm(self):
105 """Redirect old /+builds requests to new URL, /builders."""
106@@ -785,7 +850,7 @@
107 'branches': IBranchSet,
108 'bugs': IMaloneApplication,
109 'builders': IBuilderSet,
110- '+code': IBazaarApplication,
111+ '+code-index': IBazaarApplication,
112 '+code-imports': ICodeImportSet,
113 'codeofconduct': ICodeOfConductSet,
114 '+countries': ICountrySet,
115
116=== modified file 'lib/lp/app/browser/linkchecker.py'
117--- lib/lp/app/browser/linkchecker.py 2013-01-07 02:40:55 +0000
118+++ lib/lp/app/browser/linkchecker.py 2016-06-22 21:05:27 +0000
119@@ -19,6 +19,7 @@
120 NoSuchBranch,
121 )
122 from lp.code.interfaces.branchlookup import IBranchLookup
123+from lp.code.interfaces.gitlookup import IGitLookup
124 from lp.registry.interfaces.product import InvalidProductName
125 from lp.services.searchbuilder import any
126 from lp.services.webapp import LaunchpadView
127@@ -34,7 +35,7 @@
128 something appropriate.
129
130 This initial implementation supports processing links like the following:
131- /+branch/foo/bar
132+ /+code/foo/bar
133
134 The implementation can easily be extended to handle other forms by
135 providing a method to handle the link type extracted from the json
136@@ -68,17 +69,19 @@
137 return simplejson.dumps(result)
138
139 def check_branch_links(self, links):
140- """Check links of the form /+branch/foo/bar"""
141+ """Check links of the form /+code/foo/bar"""
142 invalid_links = {}
143- branch_lookup = getUtility(IBranchLookup)
144+ bzr_branch_lookup = getUtility(IBranchLookup)
145+ git_branch_lookup = getUtility(IGitLookup)
146 for link in links:
147- path = link.strip('/')[len('+branch/'):]
148- try:
149- branch_lookup.getByLPPath(path)
150- except (CannotHaveLinkedBranch, InvalidNamespace,
151- InvalidProductName, NoLinkedBranch, NoSuchBranch,
152- NotFoundError) as e:
153- invalid_links[link] = self._error_message(e)
154+ path = link.strip('/')[len('+code/'):]
155+ if git_branch_lookup.getByPath(path)[0] is None:
156+ try:
157+ bzr_branch_lookup.getByLPPath(path)
158+ except (CannotHaveLinkedBranch, InvalidNamespace,
159+ InvalidProductName, NoLinkedBranch, NoSuchBranch,
160+ NotFoundError) as e:
161+ invalid_links[link] = self._error_message(e)
162 return {'invalid': invalid_links}
163
164 def check_bug_links(self, links):
165
166=== modified file 'lib/lp/app/browser/stringformatter.py'
167--- lib/lp/app/browser/stringformatter.py 2015-10-26 14:54:43 +0000
168+++ lib/lp/app/browser/stringformatter.py 2016-06-22 21:05:27 +0000
169@@ -455,7 +455,7 @@
170 if path.isdigit():
171 return FormattersAPI._linkify_bug_number(
172 lp_url, path, trailers)
173- url = '/+branch/%s' % path
174+ url = '/+code/%s' % path
175 # Mark the links with a 'branch-short-link' class so they can be
176 # harvested and validated when the page is rendered.
177 return structured(
178
179=== modified file 'lib/lp/app/browser/tests/test_launchpad.py'
180--- lib/lp/app/browser/tests/test_launchpad.py 2016-04-11 06:38:48 +0000
181+++ lib/lp/app/browser/tests/test_launchpad.py 2016-06-22 21:05:27 +0000
182@@ -21,10 +21,12 @@
183 from lp.app.errors import GoneError
184 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
185 from lp.app.interfaces.services import IService
186+from lp.code.interfaces.gitrepository import IGitRepositorySet
187 from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
188 from lp.registry.enums import (
189 PersonVisibility,
190 SharingPermission,
191+ VCSType,
192 )
193 from lp.registry.interfaces.person import IPersonSet
194 from lp.services.identity.interfaces.account import AccountStatus
195@@ -37,10 +39,11 @@
196 from lp.services.webapp.servers import LaunchpadTestRequest
197 from lp.services.webapp.url import urlappend
198 from lp.testing import (
199+ admin_logged_in,
200 ANONYMOUS,
201 celebrity_logged_in,
202+ login_person,
203 login,
204- login_person,
205 person_logged_in,
206 TestCaseWithFactory,
207 )
208@@ -332,6 +335,253 @@
209 self.assertNotFound("_foo", use_default_referer=False)
210
211
212+class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
213+
214+ layer = DatabaseFunctionalLayer
215+
216+ def traverse(self, path, **kwargs):
217+ return super(TestCodeTraversal, self).traverse(
218+ path, '+code', **kwargs)
219+
220+ def test_project_bzr_branch(self):
221+ branch = self.factory.makeAnyBranch()
222+ self.assertRedirects(branch.unique_name, canonical_url(branch))
223+
224+ def test_project_git_branch(self):
225+ git_repo = self.factory.makeGitRepository()
226+ self.assertRedirects(git_repo.unique_name, canonical_url(git_repo))
227+
228+ def test_no_such_bzr_unique_name(self):
229+ branch = self.factory.makeAnyBranch()
230+ bad_name = branch.unique_name + 'wibble'
231+ self.assertNotFound(bad_name)
232+
233+ def test_no_such_git_unique_name(self):
234+ repo = self.factory.makeGitRepository()
235+ bad_name = repo.unique_name + 'wibble'
236+ self.assertNotFound(bad_name)
237+
238+ def test_private_bzr_branch(self):
239+ branch = self.factory.makeProductBranch(
240+ information_type=InformationType.USERDATA)
241+ branch_unique_name = removeSecurityProxy(branch).unique_name
242+ login(ANONYMOUS)
243+ self.assertNotFound(branch_unique_name)
244+
245+ def test_private_git_branch(self):
246+ git_repo = self.factory.makeGitRepository(
247+ information_type=InformationType.USERDATA)
248+ repo_unique_name = removeSecurityProxy(git_repo).unique_name
249+ login(ANONYMOUS)
250+ self.assertNotFound(repo_unique_name)
251+
252+ def test_product_alias_bzr(self):
253+ branch = self.factory.makeProductBranch()
254+ naked_product = removeSecurityProxy(branch.product)
255+ ICanHasLinkedBranch(naked_product).setBranch(branch)
256+ self.assertRedirects(naked_product.name, canonical_url(branch))
257+
258+ def test_product_alias_git(self):
259+ project = self.factory.makeProduct()
260+ repo = self.factory.makeGitRepository(target=project)
261+ naked_project = removeSecurityProxy(project)
262+ with person_logged_in(repo.target.owner):
263+ getUtility(IGitRepositorySet).setDefaultRepository(
264+ repo.target, repo)
265+ self.assertRedirects(naked_project.name, canonical_url(repo))
266+
267+ def test_private_bzr_branch_for_product(self):
268+ branch = self.factory.makeProductBranch()
269+ naked_product = removeSecurityProxy(branch.product)
270+ ICanHasLinkedBranch(naked_product).setBranch(branch)
271+ removeSecurityProxy(branch).information_type = (
272+ InformationType.USERDATA)
273+ login(ANONYMOUS)
274+ self.assertNotFound(naked_product.name)
275+
276+ def test_private_git_branch_for_product(self):
277+ project = self.factory.makeProduct()
278+ repo = self.factory.makeGitRepository(target=project)
279+ with person_logged_in(repo.target.owner):
280+ getUtility(IGitRepositorySet).setDefaultRepository(
281+ repo.target, repo)
282+
283+ removeSecurityProxy(repo).information_type = (
284+ InformationType.USERDATA)
285+ login(ANONYMOUS)
286+
287+ naked_project = removeSecurityProxy(project)
288+ self.assertNotFound(naked_project.name)
289+
290+ def test_nonexistent_product(self):
291+ non_existent = 'non-existent'
292+ self.assertNotFound(non_existent)
293+
294+ def test_product_without_dev_focus(self):
295+ product = self.factory.makeProduct()
296+ self.assertNotFound(product.name)
297+
298+ def test_distro_package_alias_bzr(self):
299+ sourcepackage = self.factory.makeSourcePackage()
300+ branch = self.factory.makePackageBranch(sourcepackage=sourcepackage)
301+ distro_package = sourcepackage.distribution_sourcepackage
302+ registrant = distro_package.distribution.owner
303+ target = ICanHasLinkedBranch(distro_package)
304+ with person_logged_in(registrant):
305+ target.setBranch(branch, registrant)
306+ self.assertRedirects("%s" % target.bzr_path, canonical_url(branch))
307+
308+ def test_distro_package_alias_git(self):
309+ sourcepackage = self.factory.makeSourcePackage()
310+ distro_package = sourcepackage.distribution_sourcepackage
311+ repo = self.factory.makeGitRepository(target=distro_package)
312+
313+ with admin_logged_in():
314+ getUtility(IGitRepositorySet).setDefaultRepository(
315+ distro_package, repo)
316+
317+ self.assertRedirects("%s" % repo.shortened_path, canonical_url(repo))
318+
319+ def test_private_branch_for_distro_package_bzr(self):
320+ sourcepackage = self.factory.makeSourcePackage()
321+ branch = self.factory.makePackageBranch(
322+ sourcepackage=sourcepackage,
323+ information_type=InformationType.USERDATA)
324+ distro_package = sourcepackage.distribution_sourcepackage
325+ registrant = distro_package.distribution.owner
326+ with person_logged_in(registrant):
327+ ICanHasLinkedBranch(distro_package).setBranch(branch, registrant)
328+ login(ANONYMOUS)
329+ path = ICanHasLinkedBranch(distro_package).bzr_path
330+ self.assertNotFound(path)
331+
332+ def test_private_branch_for_distro_package_git(self):
333+ sourcepackage = self.factory.makeSourcePackage()
334+ distro_package = sourcepackage.distribution_sourcepackage
335+ repo = self.factory.makeGitRepository(
336+ target=distro_package,
337+ information_type=InformationType.USERDATA)
338+ with admin_logged_in():
339+ getUtility(IGitRepositorySet).setDefaultRepository(
340+ distro_package, repo)
341+ login(ANONYMOUS)
342+ path = removeSecurityProxy(repo).shortened_path
343+ self.assertNotFound(path)
344+
345+ def test_trailing_path_redirect_bzr(self):
346+ branch = self.factory.makeAnyBranch()
347+ path = urlappend(branch.unique_name, '+edit')
348+ self.assertRedirects(path, canonical_url(branch, view_name='+edit'))
349+
350+ def test_trailing_path_redirect_git(self):
351+ repo = self.factory.makeGitRepository()
352+ path = urlappend(repo.unique_name, '+edit')
353+ self.assertRedirects(path, canonical_url(repo, view_name='+edit'))
354+
355+ def test_alias_trailing_path_redirect_bzr(self):
356+ branch = self.factory.makeProductBranch()
357+ with person_logged_in(branch.product.owner):
358+ branch.product.development_focus.branch = branch
359+ path = '%s/+edit' % branch.product.name
360+ self.assertRedirects(path, canonical_url(branch, view_name='+edit'))
361+
362+ def test_alias_trailing_path_redirect_git(self):
363+ project = self.factory.makeProduct()
364+ repo = self.factory.makeGitRepository(target=project)
365+ with admin_logged_in():
366+ getUtility(IGitRepositorySet).setDefaultRepository(
367+ project, repo)
368+ path = '%s/+edit' % project.name
369+ self.assertRedirects(path, canonical_url(repo, view_name='+edit'))
370+
371+ def test_product_series_redirect_bzr(self):
372+ branch = self.factory.makeBranch()
373+ series = self.factory.makeProductSeries(branch=branch)
374+ self.assertRedirects(
375+ ICanHasLinkedBranch(series).bzr_path, canonical_url(branch))
376+
377+ def test_no_branch_for_series(self):
378+ # If there's no branch for a product series, display a
379+ # message telling the user there is no linked branch.
380+ series = self.factory.makeProductSeries()
381+ path = ICanHasLinkedBranch(series).bzr_path
382+ self.assertNotFound(path)
383+
384+ def test_private_branch_for_series(self):
385+ # If the development focus of a product series is private, display a
386+ # message telling the user there is no linked branch.
387+ branch = self.factory.makeBranch(
388+ information_type=InformationType.USERDATA)
389+ series = self.factory.makeProductSeries(branch=branch)
390+ login(ANONYMOUS)
391+ path = ICanHasLinkedBranch(series).bzr_path
392+ self.assertNotFound(path)
393+
394+ def test_too_short_branch_name(self):
395+ owner = self.factory.makePerson()
396+ self.assertNotFound('~%s' % owner.name)
397+
398+ def test_invalid_product_name(self):
399+ self.assertNotFound('_foo')
400+
401+ def test_invalid_product_name_without_referer(self):
402+ self.assertNotFound("_foo", use_default_referer=False)
403+
404+ def test_ambiguous_project_default_repo_bzr(self):
405+ project = self.factory.makeProduct()
406+ bzr_branch = self.factory.makeBranch(target=project)
407+ self.factory.makeGitRepository(target=project)
408+ with person_logged_in(project.owner):
409+ ICanHasLinkedBranch(project).setBranch(bzr_branch, project.owner)
410+
411+ self.assertRedirects(project.name, canonical_url(bzr_branch))
412+
413+ def test_ambiguous_project_without_vcs_set(self):
414+ project = self.factory.makeProduct()
415+ bzr_branch = self.factory.makeBranch(target=project)
416+ repo = self.factory.makeGitRepository(target=project)
417+ with person_logged_in(project.owner):
418+ ICanHasLinkedBranch(project).setBranch(bzr_branch, project.owner)
419+ getUtility(IGitRepositorySet).setDefaultRepository(
420+ project, repo)
421+
422+ self.assertNotFound(project.name)
423+
424+ def test_ambiguous_project_with_vcs_set_to_git(self):
425+ project = self.factory.makeProduct()
426+ bzr_branch = self.factory.makeBranch(target=project)
427+ repo = self.factory.makeGitRepository(target=project)
428+ with person_logged_in(project.owner):
429+ ICanHasLinkedBranch(project).setBranch(bzr_branch, project.owner)
430+ getUtility(IGitRepositorySet).setDefaultRepository(
431+ project, repo)
432+ project.vcs = VCSType.GIT
433+
434+ self.assertRedirects(project.name, canonical_url(repo))
435+
436+ def test_ambiguous_project_with_vcs_set_to_bzr(self):
437+ project = self.factory.makeProduct()
438+ bzr_branch = self.factory.makeBranch(target=project)
439+ repo = self.factory.makeGitRepository(target=project)
440+ with person_logged_in(project.owner):
441+ ICanHasLinkedBranch(project).setBranch(bzr_branch, project.owner)
442+ getUtility(IGitRepositorySet).setDefaultRepository(
443+ project, repo)
444+ project.vcs = VCSType.BZR
445+
446+ self.assertRedirects(project.name, canonical_url(bzr_branch))
447+
448+ def test_personal_branch_bzr(self):
449+ person = self.factory.makePerson()
450+ branch = self.factory.makePersonalBranch(owner=person)
451+ self.assertRedirects(branch.unique_name, canonical_url(branch))
452+
453+ def test_personal_branch_git(self):
454+ person = self.factory.makePerson()
455+ repo = self.factory.makeGitRepository(owner=person, target=person)
456+ self.assertRedirects(repo.unique_name, canonical_url(repo))
457+
458+
459 class TestPersonTraversal(TestCaseWithFactory, TraversalMixin):
460
461 layer = DatabaseFunctionalLayer
462
463=== modified file 'lib/lp/app/browser/tests/test_linkchecker.py'
464--- lib/lp/app/browser/tests/test_linkchecker.py 2012-09-17 15:19:10 +0000
465+++ lib/lp/app/browser/tests/test_linkchecker.py 2016-06-22 21:05:27 +0000
466@@ -21,7 +21,7 @@
467
468 layer = DatabaseFunctionalLayer
469
470- BRANCH_URL_TEMPLATE = '/+branch/%s'
471+ BRANCH_URL_TEMPLATE = '/+code/%s'
472
473 def check_invalid_links(self, result_json, link_type, invalid_links):
474 link_dict = simplejson.loads(result_json)
475@@ -43,9 +43,22 @@
476 removeSecurityProxy(product).development_focus.branch = product_branch
477 valid_product_url = self.BRANCH_URL_TEMPLATE % product.name
478
479+ # git branches are a thing now as well!
480+ project_git_repo = removeSecurityProxy(
481+ self.factory.makeGitRepository(target=product))
482+ dsp = self.factory.makeDistributionSourcePackage()
483+ dsp_git_repo = removeSecurityProxy(
484+ self.factory.makeGitRepository(target=dsp))
485+ person = self.factory.makePerson()
486+ person_git_repo = removeSecurityProxy(
487+ self.factory.makeGitRepository(target=person))
488+
489 return [
490 valid_branch_url,
491 valid_product_url,
492+ self.BRANCH_URL_TEMPLATE % project_git_repo.unique_name,
493+ self.BRANCH_URL_TEMPLATE % dsp_git_repo.unique_name,
494+ self.BRANCH_URL_TEMPLATE % person_git_repo.unique_name,
495 ]
496
497 def make_invalid_branch_links(self):
498
499=== modified file 'lib/lp/app/doc/displaying-paragraphs-of-text.txt'
500--- lib/lp/app/doc/displaying-paragraphs-of-text.txt 2015-07-21 09:04:01 +0000
501+++ lib/lp/app/doc/displaying-paragraphs-of-text.txt 2016-06-22 21:05:27 +0000
502@@ -366,17 +366,17 @@
503 ... 'lp:///foo\n'
504 ... 'lp:/foo\n')
505 >>> print test_tales('foo/fmt:text-to-html', foo=text)
506- <p><a href="/+branch/~foo/bar/baz" class="...">lp:~foo/bar/baz</a><br />
507- <a href="/+branch/~foo/bar/bug-123" class="...">lp:~foo/bar/bug-123</a><br />
508- <a href="/+branch/~foo/+junk/baz" class="...">lp:~foo/+junk/baz</a><br />
509- <a href="/+branch/~foo/ubuntu/jaunty/evolution/baz" class="...">lp:~foo/ubuntu/jaunty/evolution/baz</a><br />
510- <a href="/+branch/foo/bar" class="...">lp:foo/bar</a><br />
511- <a href="/+branch/foo" class="...">lp:foo</a><br />
512- <a href="/+branch/foo" class="...">lp:foo</a>,<br />
513- <a href="/+branch/foo/bar" class="...">lp:foo/bar</a>.<br />
514- <a href="/+branch/foo/bar/baz" class="...">lp:foo/bar/baz</a><br />
515- <a href="/+branch/foo" class="...">lp:///foo</a><br />
516- <a href="/+branch/foo" class="...">lp:/foo</a></p>
517+ <p><a href="/+code/~foo/bar/baz" class="...">lp:~foo/bar/baz</a><br />
518+ <a href="/+code/~foo/bar/bug-123" class="...">lp:~foo/bar/bug-123</a><br />
519+ <a href="/+code/~foo/+junk/baz" class="...">lp:~foo/+junk/baz</a><br />
520+ <a href="/+code/~foo/ubuntu/jaunty/evolution/baz" class="...">lp:~foo/ubuntu/jaunty/evolution/baz</a><br />
521+ <a href="/+code/foo/bar" class="...">lp:foo/bar</a><br />
522+ <a href="/+code/foo" class="...">lp:foo</a><br />
523+ <a href="/+code/foo" class="...">lp:foo</a>,<br />
524+ <a href="/+code/foo/bar" class="...">lp:foo/bar</a>.<br />
525+ <a href="/+code/foo/bar/baz" class="...">lp:foo/bar/baz</a><br />
526+ <a href="/+code/foo" class="...">lp:///foo</a><br />
527+ <a href="/+code/foo" class="...">lp:/foo</a></p>
528
529 Text that looks like a branch reference, but is followed only by digits is
530 treated as a link to a bug.
531
532=== modified file 'lib/lp/app/javascript/lp-links.js'
533--- lib/lp/app/javascript/lp-links.js 2012-05-16 19:21:27 +0000
534+++ lib/lp/app/javascript/lp-links.js 2016-06-22 21:05:27 +0000
535@@ -56,7 +56,7 @@
536 Y.lp.app.links.check_valid_lp_links = function(io_provider) {
537 // Grabs any lp: style links on the page and checks that they are
538 // valid. Invalid ones have their class changed to "invalid-link".
539- // ATM, we only handle +branch and bug links.
540+ // ATM, we only handle +code and bug links.
541
542 var links_to_check = {};
543
544@@ -108,4 +108,3 @@
545 };
546
547 }, "0.1", {"requires": ["base", "node", "io", "dom", "json", "lp.client"]});
548-
549
550=== modified file 'lib/lp/app/javascript/tests/test_lp_links.html'
551--- lib/lp/app/javascript/tests/test_lp_links.html 2012-10-26 09:54:28 +0000
552+++ lib/lp/app/javascript/tests/test_lp_links.html 2016-06-22 21:05:27 +0000
553@@ -50,8 +50,8 @@
554 <div id="expected-id">
555 <a href="/bugs/14" class="bug-link" id="valid-bug">bug 14</a>
556 <a href="/bugs/200" class="bug-link" id="invalid-bug">bug 200</a>
557- <a href="/+branch/valid" class="branch-short-link" id="valid-branch">lp:valid</a>
558- <a href="/+branch/invalid" class="branch-short-link" id="invalid-branch">lp:invalid</a>
559+ <a href="/+code/valid" class="branch-short-link" id="valid-branch">lp:valid</a>
560+ <a href="/+code/invalid" class="branch-short-link" id="invalid-branch">lp:invalid</a>
561 </div>
562 </body>
563 </html>
564
565=== modified file 'lib/lp/app/javascript/tests/test_lp_links.js'
566--- lib/lp/app/javascript/tests/test_lp_links.js 2012-10-26 10:00:20 +0000
567+++ lib/lp/app/javascript/tests/test_lp_links.js 2016-06-22 21:05:27 +0000
568@@ -20,7 +20,7 @@
569 "/bugs/200": "Bug 200 cannot be found"}},
570 "branch_links": {
571 "invalid": {
572- "/+branch/invalid":
573+ "/+code/invalid":
574 "No such product: 'invalid'."}}};
575 mock_io.success({
576 responseText: Y.JSON.stringify(response),
577@@ -83,7 +83,7 @@
578 var response = {
579 "branch_links": {
580 "invalid": {
581- "/+branch/invalid":
582+ "/+code/invalid":
583 "No such product: 'invalid'."}}};
584 mock_io.success({
585 responseText: Y.JSON.stringify(response),
586
587=== modified file 'lib/lp/app/stories/basics/notfound-traversals.txt'
588--- lib/lp/app/stories/basics/notfound-traversals.txt 2015-10-06 06:48:01 +0000
589+++ lib/lp/app/stories/basics/notfound-traversals.txt 2016-06-22 21:05:27 +0000
590@@ -95,6 +95,10 @@
591 >>> check_redirect(
592 ... "/~name12/+branch/gnome-terminal/pushed/+edit",
593 ... auth=True, status=301)
594+ >>> check_redirect("/~name12/+branch/gnome-terminal/pushed/", status=301)
595+ >>> check_redirect(
596+ ... "/~name12/+branch/gnome-terminal/pushed/+edit",
597+ ... auth=True, status=301)
598 >>> check_redirect("/~name16/+packages", status=301)
599 >>> check_redirect("/~name16/+projects", status=301)
600 >>> check_redirect("/+builds", status=301)