Merge lp:~blr/launchpad/bug-1600055-configure-code-remote-username into lp:launchpad

Proposed by Kit Randel
Status: Merged
Approved by: Kit Randel
Approved revision: no longer in the source branch.
Merged at revision: 18152
Proposed branch: lp:~blr/launchpad/bug-1600055-configure-code-remote-username
Merge into: lp:launchpad
Diff against target: 232 lines (+93/-83)
4 files modified
lib/lp/code/templates/configure-code-macros.pt (+1/-1)
lib/lp/registry/browser/product.py (+11/-0)
lib/lp/registry/browser/tests/test_product.py (+81/-0)
lib/lp/registry/browser/tests/test_product_views.py (+0/-82)
To merge this branch: bzr merge lp:~blr/launchpad/bug-1600055-configure-code-remote-username
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+299774@code.launchpad.net

Commit message

Add view property to format git ssh url with username.

Description of the change

Unless your launchpad username aligns with your local username, or you have configured your lp username in ~/.ssh/config, instructions for adding remote without a username are invalid.

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

In addition to the comments below, please could you add some tests for this?

review: Needs Fixing
Revision history for this message
Kit Randel (blr) wrote :

Colin, have moved the tests in browser.tests.test_product_views to browser.tests.test_product as the bulk of product view tests were already there. I'm not entirely sure why these were in different modules, unless we had intended to move to a file name convention of test_foo_views.py for view tests. Given the browser namespace, I think it is already reasonably obvious that these are view tests and we could probably avoid the explicit 'views' postfix.

Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/templates/configure-code-macros.pt'
2--- lib/lp/code/templates/configure-code-macros.pt 2016-01-14 13:55:43 +0000
3+++ lib/lp/code/templates/configure-code-macros.pt 2016-07-25 04:46:32 +0000
4@@ -20,7 +20,7 @@
5 command:</p>
6 <p>
7 <code class="command command-block">
8- git remote add origin <tal:project replace="modules/lp.services.config/config/codehosting/git_ssh_root"/><tal:project replace="context/name"/><br />
9+ git remote add origin <span tal:content="string:${view/git_ssh_url}"></span><br />
10 </code></p>
11 <p>&hellip; and push the Git branch to Launchpad with:</p>
12 <p>
13
14=== modified file 'lib/lp/registry/browser/product.py'
15--- lib/lp/registry/browser/product.py 2016-04-14 05:16:26 +0000
16+++ lib/lp/registry/browser/product.py 2016-07-25 04:46:32 +0000
17@@ -42,7 +42,9 @@
18
19
20 from operator import attrgetter
21+from urlparse import urlunsplit
22
23+from bzrlib import urlutils
24 from bzrlib.revision import NULL_REVISION
25 from lazr.delegates import delegate_to
26 from lazr.restful.interface import (
27@@ -212,6 +214,7 @@
28 from lp.services.webapp.breadcrumb import Breadcrumb
29 from lp.services.webapp.interfaces import UnsafeFormGetSubmissionError
30 from lp.services.webapp.menu import NavigationMenu
31+from lp.services.webapp.url import urlsplit
32 from lp.services.webapp.vhosts import allvhosts
33 from lp.services.worlddata.helpers import browser_languages
34 from lp.services.worlddata.interfaces.country import ICountry
35@@ -1743,6 +1746,14 @@
36 self.context.pillar))
37
38 @property
39+ def git_ssh_url(self):
40+ base_url = urlsplit(
41+ urlutils.join(config.codehosting.git_ssh_root, self.context.name))
42+ url = list(base_url)
43+ url[1] = "{}@{}".format(self.user.name, base_url.hostname)
44+ return urlunsplit(url)
45+
46+ @property
47 def next_url(self):
48 """Return the next_url.
49
50
51=== modified file 'lib/lp/registry/browser/tests/test_product.py'
52--- lib/lp/registry/browser/tests/test_product.py 2015-10-01 17:32:41 +0000
53+++ lib/lp/registry/browser/tests/test_product.py 2016-07-25 04:46:32 +0000
54@@ -7,6 +7,9 @@
55
56 __all__ = ['make_product_form']
57
58+import re
59+from urlparse import urlsplit
60+
61 from lazr.restful.interfaces import IJSONRequestCache
62 from soupmatchers import (
63 HTMLContains,
64@@ -834,3 +837,81 @@
65 with person_logged_in(owner):
66 self.assertIn(public.name, browser.contents)
67 self.assertIn(proprietary.name, browser.contents)
68+
69+
70+class TestProductSetBranchView(TestCaseWithFactory):
71+
72+ layer = DatabaseFunctionalLayer
73+
74+ def test_git_ssh_url(self):
75+ project = self.factory.makeProduct()
76+ with person_logged_in(project.owner):
77+ view = create_initialized_view(
78+ project, '+configure-code', principal=project.owner,
79+ method='GET')
80+ git_ssh_url = 'git+ssh://{username}@{host}/{project}'.format(
81+ username=project.owner.name,
82+ host=urlsplit(config.codehosting.git_ssh_root).hostname,
83+ project=project.name)
84+ self.assertEqual(git_ssh_url, view.git_ssh_url)
85+
86+
87+class TestBrowserProductSetBranchView(BrowserTestCase):
88+
89+ layer = DatabaseFunctionalLayer
90+
91+ editsshkeys_tag = Tag(
92+ 'edit SSH keys', 'a', text=re.compile('register an SSH key'),
93+ attrs={'href': re.compile(r'/\+editsshkeys$')})
94+
95+ def getBrowser(self, project, view_name=None):
96+ project = removeSecurityProxy(project)
97+ url = canonical_url(project, view_name=view_name)
98+ return self.getUserBrowser(url, project.owner)
99+
100+ def test_no_initial_git_repository(self):
101+ # If a project has no default Git repository, its "Git repository"
102+ # control defaults to empty.
103+ project = self.factory.makeProduct()
104+ browser = self.getBrowser(project, '+configure-code')
105+ self.assertEqual('', browser.getControl('Git repository').value)
106+
107+ def test_initial_git_repository(self):
108+ # If a project has a default Git repository, its "Git repository"
109+ # control defaults to the unique name of that repository.
110+ project = self.factory.makeProduct()
111+ repo = self.factory.makeGitRepository(target=project)
112+ with person_logged_in(project.owner):
113+ getUtility(IGitRepositorySet).setDefaultRepository(project, repo)
114+ unique_name = repo.unique_name
115+ browser = self.getBrowser(project, '+configure-code')
116+ self.assertEqual(
117+ unique_name, browser.getControl('Git repository').value)
118+
119+ def test_link_existing_git_repository(self):
120+ repo = removeSecurityProxy(self.factory.makeGitRepository(
121+ target=self.factory.makeProduct()))
122+ browser = self.getBrowser(repo.project, '+configure-code')
123+ browser.getControl('Git', index=0).click()
124+ browser.getControl('Git repository').value = repo.shortened_path
125+ browser.getControl('Update').click()
126+
127+ tag = Tag(
128+ 'success-div', 'div', attrs={'class': 'informational message'},
129+ text='Project settings updated.')
130+ self.assertThat(browser.contents, HTMLContains(tag))
131+
132+ def test_editsshkeys_link_if_no_keys_registered(self):
133+ project = self.factory.makeProduct()
134+ browser = self.getBrowser(project, '+configure-code')
135+ self.assertThat(
136+ browser.contents, HTMLContains(self.editsshkeys_tag))
137+
138+ def test_no_editsshkeys_link_if_keys_registered(self):
139+ project = self.factory.makeProduct()
140+ with person_logged_in(project.owner):
141+ self.factory.makeSSHKey(person=project.owner)
142+ browser = self.getBrowser(project, '+configure-code')
143+ self.assertThat(
144+ browser.contents,
145+ Not(HTMLContains(self.editsshkeys_tag)))
146
147=== removed file 'lib/lp/registry/browser/tests/test_product_views.py'
148--- lib/lp/registry/browser/tests/test_product_views.py 2016-01-14 13:55:43 +0000
149+++ lib/lp/registry/browser/tests/test_product_views.py 1970-01-01 00:00:00 +0000
150@@ -1,82 +0,0 @@
151-# Copyright 2011-2016 Canonical Ltd. This software is licensed under the
152-# GNU Affero General Public License version 3 (see the file LICENSE).
153-
154-"""View tests for Product pages."""
155-
156-__metaclass__ = type
157-
158-import re
159-
160-from testtools.matchers import Not
161-import soupmatchers
162-from zope.component import getUtility
163-from zope.security.proxy import removeSecurityProxy
164-
165-from lp.code.interfaces.gitrepository import IGitRepositorySet
166-from lp.services.webapp import canonical_url
167-from lp.testing import (
168- BrowserTestCase,
169- person_logged_in,
170- )
171-from lp.testing.layers import DatabaseFunctionalLayer
172-
173-
174-class TestProductSetBranchView(BrowserTestCase):
175-
176- layer = DatabaseFunctionalLayer
177-
178- editsshkeys_tag = soupmatchers.Tag(
179- 'edit SSH keys', 'a', text=re.compile('register an SSH key'),
180- attrs={'href': re.compile(r'/\+editsshkeys$')})
181-
182- def getBrowser(self, project, view_name=None):
183- project = removeSecurityProxy(project)
184- url = canonical_url(project, view_name=view_name)
185- return self.getUserBrowser(url, project.owner)
186-
187- def test_no_initial_git_repository(self):
188- # If a project has no default Git repository, its "Git repository"
189- # control defaults to empty.
190- project = self.factory.makeProduct()
191- browser = self.getBrowser(project, '+configure-code')
192- self.assertEqual('', browser.getControl('Git repository').value)
193-
194- def test_initial_git_repository(self):
195- # If a project has a default Git repository, its "Git repository"
196- # control defaults to the unique name of that repository.
197- project = self.factory.makeProduct()
198- repo = self.factory.makeGitRepository(target=project)
199- with person_logged_in(project.owner):
200- getUtility(IGitRepositorySet).setDefaultRepository(project, repo)
201- unique_name = repo.unique_name
202- browser = self.getBrowser(project, '+configure-code')
203- self.assertEqual(
204- unique_name, browser.getControl('Git repository').value)
205-
206- def test_link_existing_git_repository(self):
207- repo = removeSecurityProxy(self.factory.makeGitRepository(
208- target=self.factory.makeProduct()))
209- browser = self.getBrowser(repo.project, '+configure-code')
210- browser.getControl('Git', index=0).click()
211- browser.getControl('Git repository').value = repo.shortened_path
212- browser.getControl('Update').click()
213-
214- tag = soupmatchers.Tag(
215- 'success-div', 'div', attrs={'class': 'informational message'},
216- text='Project settings updated.')
217- self.assertThat(browser.contents, soupmatchers.HTMLContains(tag))
218-
219- def test_editsshkeys_link_if_no_keys_registered(self):
220- project = self.factory.makeProduct()
221- browser = self.getBrowser(project, '+configure-code')
222- self.assertThat(
223- browser.contents, soupmatchers.HTMLContains(self.editsshkeys_tag))
224-
225- def test_no_editsshkeys_link_if_keys_registered(self):
226- project = self.factory.makeProduct()
227- with person_logged_in(project.owner):
228- self.factory.makeSSHKey(person=project.owner)
229- browser = self.getBrowser(project, '+configure-code')
230- self.assertThat(
231- browser.contents,
232- Not(soupmatchers.HTMLContains(self.editsshkeys_tag)))