13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
"""Directory lookup that uses Launchpad."""
20
from urlparse import urlsplit
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
"""Transport indirection that uses Launchpad as a directory lookup.
20
When the transport is opened, it immediately redirects to a url
21
on Launchpad, which can then either serve the branch itself or redirect
23
25
from bzrlib import (
29
from bzrlib.i18n import gettext
31
from bzrlib.plugins.launchpad.lp_registration import (
32
LaunchpadService, ResolveLaunchpadPathRequest)
33
from bzrlib.plugins.launchpad.account import get_lp_login
36
# As bzrlib.transport.remote may not be loaded yet, make sure bzr+ssh
37
# is counted as a netloc protocol.
38
transport.register_urlparse_netloc_protocol('bzr+ssh')
39
transport.register_urlparse_netloc_protocol('lp')
41
_ubuntu_series_shortcuts = {
52
class LaunchpadDirectory(object):
54
def _requires_launchpad_login(self, scheme, netloc, path, query,
56
"""Does the URL require a Launchpad login in order to be reached?
58
The URL is specified by its parsed components, as returned from
61
return (scheme in ('bzr+ssh', 'sftp')
62
and (netloc.endswith('launchpad.net')
63
or netloc.endswith('launchpad.dev')))
65
def look_up(self, name, url):
66
"""See DirectoryService.look_up"""
67
return self._resolve(url)
69
def _resolve_locally(self, path, url, _request_factory):
70
# This is the best I could work out about XMLRPC. If an lp: url
71
# includes ~user, then it is specially validated. Otherwise, it is just
72
# sent to +branch/$path.
73
_, netloc, _, _, _ = urlsplit(url)
75
netloc = LaunchpadService.DEFAULT_INSTANCE
76
base_url = LaunchpadService.LAUNCHPAD_DOMAINS[netloc]
77
base = 'bzr+ssh://bazaar.%s/' % (base_url,)
79
if path.startswith('~'):
80
# A ~user style path, validate it a bit.
81
# If a path looks fishy, fall back to asking XMLRPC to
82
# resolve it for us. That way we still get their nicer error
84
parts = path.split('/')
86
or (parts[1] in ('ubuntu', 'debian') and len(parts) < 5)):
87
# This special case requires 5-parts to be valid.
92
return self._resolve_via_xmlrpc(path, url, _request_factory)
93
return {'urls': [base + path]}
95
def _resolve_via_xmlrpc(self, path, url, _request_factory):
96
service = LaunchpadService.for_url(url)
97
resolve = _request_factory(path)
99
result = resolve.submit(service)
100
except xmlrpclib.Fault, fault:
101
raise errors.InvalidURL(
102
path=url, extra=fault.faultString)
105
def _update_url_scheme(self, url):
106
# Do ubuntu: and debianlp: expansions.
107
scheme, netloc, path, query, fragment = urlsplit(url)
108
if scheme in ('ubuntu', 'debianlp'):
109
if scheme == 'ubuntu':
111
distro_series = _ubuntu_series_shortcuts
112
elif scheme == 'debianlp':
114
# No shortcuts for Debian distroseries.
117
raise AssertionError('scheme should be ubuntu: or debianlp:')
118
# Split the path. It's either going to be 'project' or
119
# 'series/project', but recognize that it may be a series we don't
121
path_parts = path.split('/')
122
if len(path_parts) == 1:
123
# It's just a project name.
124
lp_url_template = 'lp:%(distro)s/%(project)s'
125
project = path_parts[0]
127
elif len(path_parts) == 2:
128
# It's a series and project.
129
lp_url_template = 'lp:%(distro)s/%(series)s/%(project)s'
130
series, project = path_parts
132
# There are either 0 or > 2 path parts, neither of which is
133
# supported for these schemes.
134
raise errors.InvalidURL('Bad path: %s' % result.path)
135
# Expand any series shortcuts, but keep unknown series.
136
series = distro_series.get(series, series)
137
# Hack the url and let the following do the final resolution.
138
url = lp_url_template % dict(
142
scheme, netloc, path, query, fragment = urlsplit(url)
145
def _expand_user(self, path, url, lp_login):
146
if path.startswith('~/'):
148
raise errors.InvalidURL(path=url,
149
extra='Cannot resolve "~" to your username.'
150
' See "bzr help launchpad-login"')
151
path = '~' + lp_login + path[1:]
154
def _resolve(self, url,
155
_request_factory=ResolveLaunchpadPathRequest,
157
"""Resolve the base URL for this transport."""
158
url, path = self._update_url_scheme(url)
159
if _lp_login is None:
160
_lp_login = get_lp_login()
161
path = path.strip('/')
162
path = self._expand_user(path, url, _lp_login)
163
if _lp_login is not None:
164
result = self._resolve_locally(path, url, _request_factory)
165
if 'launchpad' in debug.debug_flags:
167
result = self._resolve_via_xmlrpc(path, url, _request_factory)
169
'resolution for {0}\n local: {1}\n remote: {2}').format(
170
url, local_res['urls'], result['urls']))
172
result = self._resolve_via_xmlrpc(path, url, _request_factory)
174
if 'launchpad' in debug.debug_flags:
175
trace.mutter("resolve_lp_path(%r) == %r", url, result)
177
_warned_login = False
178
for url in result['urls']:
179
scheme, netloc, path, query, fragment = urlsplit(url)
180
if self._requires_launchpad_login(scheme, netloc, path, query,
182
# Only accept launchpad.net bzr+ssh URLs if we know
183
# the user's Launchpad login:
184
if _lp_login is not None:
186
if _lp_login is None:
187
if not _warned_login:
189
'You have not informed bzr of your Launchpad ID, and you must do this to\n'
190
'write to Launchpad or access private data. See "bzr help launchpad-login".')
193
# Use the URL if we can create a transport for it.
195
transport.get_transport(url)
196
except (errors.PathError, errors.TransportError):
201
raise errors.InvalidURL(path=url, extra='no supported schemes')
28
from bzrlib.transport import (
34
def launchpad_transport_indirect(base_url):
35
"""Uses Launchpad.net as a directory of open source software"""
36
if base_url.startswith('lp:///'):
37
real_url = 'http://code.launchpad.net/' + base_url[6:]
38
elif base_url.startswith('lp:') and base_url[3] != '/':
39
real_url = 'http://code.launchpad.net/' + base_url[3:]
41
raise errors.InvalidURL(path=base_url)
42
return get_transport(real_url)
205
45
def get_test_permutations():