Merge lp:~thomir-deactivatedaccount/launchpad/devel-fix-git-links into lp:launchpad
- devel-fix-git-links
- Merge into devel
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 |
Related bugs: |
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.
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.
There are various other instances of '+branch' in the codebase, and I'm not sure which of them, if any need updating.
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.
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.
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.
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.
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
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) |
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 app/javascript/ tests/test_ lp_links. *
lib/lp/
... and the comment in lib/lp/ app/javascript/ lp-links. js should probably be updated too.