~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugins/launchpad/lp_indirect.py

  • Committer: Andrew Bennetts
  • Date: 2007-11-30 06:06:32 UTC
  • mto: This revision was merged to the branch mainline in revision 3057.
  • Revision ID: andrew.bennetts@canonical.com-20071130060632-jh8ii23pcpnzqq5p
Add NEWS entry.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
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
16
 
 
17
 
"""Directory lookup that uses Launchpad."""
18
 
 
19
 
from __future__ import absolute_import
20
 
 
21
 
from urlparse import urlsplit
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
"""Transport indirection that uses Launchpad as a directory lookup.
 
19
 
 
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
 
22
again.
 
23
"""
 
24
 
 
25
from urlparse import urlsplit, urlunsplit
22
26
import xmlrpclib
23
27
 
24
28
from bzrlib import (
25
29
    debug,
26
30
    errors,
27
31
    trace,
28
 
    transport,
29
 
    )
30
 
from bzrlib.i18n import gettext
 
32
    urlutils,
 
33
    )
 
34
from bzrlib.transport import (
 
35
    get_transport,
 
36
    register_urlparse_netloc_protocol,
 
37
    Transport,
 
38
    )
31
39
 
32
40
from bzrlib.plugins.launchpad.lp_registration import (
33
41
    LaunchpadService, ResolveLaunchpadPathRequest)
36
44
 
37
45
# As bzrlib.transport.remote may not be loaded yet, make sure bzr+ssh
38
46
# is counted as a netloc protocol.
39
 
transport.register_urlparse_netloc_protocol('bzr+ssh')
40
 
transport.register_urlparse_netloc_protocol('lp')
41
 
 
42
 
_ubuntu_series_shortcuts = {
43
 
    'n': 'natty',
44
 
    'm': 'maverick',
45
 
    'l': 'lucid',
46
 
    'k': 'karmic',
47
 
    'j': 'jaunty',
48
 
    'h': 'hardy',
49
 
    'd': 'dapper',
50
 
    }
51
 
 
52
 
 
53
 
class LaunchpadDirectory(object):
54
 
 
55
 
    def _requires_launchpad_login(self, scheme, netloc, path, query,
56
 
                                  fragment):
57
 
        """Does the URL require a Launchpad login in order to be reached?
58
 
 
59
 
        The URL is specified by its parsed components, as returned from
60
 
        urlsplit.
61
 
        """
62
 
        return (scheme in ('bzr+ssh', 'sftp')
63
 
                and (netloc.endswith('launchpad.net')
64
 
                     or netloc.endswith('launchpad.dev')))
65
 
 
66
 
    def look_up(self, name, url):
67
 
        """See DirectoryService.look_up"""
68
 
        return self._resolve(url)
69
 
 
70
 
    def _resolve_locally(self, path, url, _request_factory):
71
 
        # This is the best I could work out about XMLRPC. If an lp: url
72
 
        # includes ~user, then it is specially validated. Otherwise, it is just
73
 
        # sent to +branch/$path.
74
 
        _, netloc, _, _, _ = urlsplit(url)
75
 
        if netloc == '':
76
 
            netloc = LaunchpadService.DEFAULT_INSTANCE
77
 
        base_url = LaunchpadService.LAUNCHPAD_DOMAINS[netloc]
78
 
        base = 'bzr+ssh://bazaar.%s/' % (base_url,)
79
 
        maybe_invalid = False
80
 
        if path.startswith('~'):
81
 
            # A ~user style path, validate it a bit.
82
 
            # If a path looks fishy, fall back to asking XMLRPC to
83
 
            # resolve it for us. That way we still get their nicer error
84
 
            # messages.
85
 
            parts = path.split('/')
86
 
            if (len(parts) < 3
87
 
                or (parts[1] in ('ubuntu', 'debian') and len(parts) < 5)):
88
 
                # This special case requires 5-parts to be valid.
89
 
                maybe_invalid = True
90
 
        else:
91
 
            base += '+branch/'
92
 
        if maybe_invalid:
93
 
            return self._resolve_via_xmlrpc(path, url, _request_factory)
94
 
        return {'urls': [base + path]}
95
 
 
96
 
    def _resolve_via_xmlrpc(self, path, url, _request_factory):
97
 
        service = LaunchpadService.for_url(url)
 
47
register_urlparse_netloc_protocol('bzr+ssh')
 
48
register_urlparse_netloc_protocol('lp')
 
49
 
 
50
 
 
51
class LaunchpadTransport(Transport):
 
52
    """lp:/// URL transport
 
53
 
 
54
    This transport redirects requests to the real branch location
 
55
    after resolving the URL via an XMLRPC request to Launchpad.
 
56
    """
 
57
 
 
58
    def __init__(self, base):
 
59
        super(LaunchpadTransport, self).__init__(base)
 
60
        # We only support URLs without a netloc
 
61
        netloc = urlsplit(base)[1]
 
62
        if netloc != '':
 
63
            raise errors.InvalidURL(path=base)
 
64
 
 
65
    def _resolve(self, abspath,
 
66
                 _request_factory=ResolveLaunchpadPathRequest,
 
67
                 _lp_login=None):
 
68
        """Resolve the base URL for this transport."""
 
69
        path = urlsplit(abspath)[2].lstrip('/')
 
70
        # Perform an XMLRPC request to resolve the path
98
71
        resolve = _request_factory(path)
 
72
        service = LaunchpadService()
99
73
        try:
100
74
            result = resolve.submit(service)
101
75
        except xmlrpclib.Fault, fault:
102
76
            raise errors.InvalidURL(
103
 
                path=url, extra=fault.faultString)
104
 
        return result
105
 
 
106
 
    def _update_url_scheme(self, url):
107
 
        # Do ubuntu: and debianlp: expansions.
108
 
        scheme, netloc, path, query, fragment = urlsplit(url)
109
 
        if scheme in ('ubuntu', 'debianlp'):
110
 
            if scheme == 'ubuntu':
111
 
                distro = 'ubuntu'
112
 
                distro_series = _ubuntu_series_shortcuts
113
 
            elif scheme == 'debianlp':
114
 
                distro = 'debian'
115
 
                # No shortcuts for Debian distroseries.
116
 
                distro_series = {}
117
 
            else:
118
 
                raise AssertionError('scheme should be ubuntu: or debianlp:')
119
 
            # Split the path.  It's either going to be 'project' or
120
 
            # 'series/project', but recognize that it may be a series we don't
121
 
            # know about.
122
 
            path_parts = path.split('/')
123
 
            if len(path_parts) == 1:
124
 
                # It's just a project name.
125
 
                lp_url_template = 'lp:%(distro)s/%(project)s'
126
 
                project = path_parts[0]
127
 
                series = None
128
 
            elif len(path_parts) == 2:
129
 
                # It's a series and project.
130
 
                lp_url_template = 'lp:%(distro)s/%(series)s/%(project)s'
131
 
                series, project = path_parts
132
 
            else:
133
 
                # There are either 0 or > 2 path parts, neither of which is
134
 
                # supported for these schemes.
135
 
                raise errors.InvalidURL('Bad path: %s' % url)
136
 
            # Expand any series shortcuts, but keep unknown series.
137
 
            series = distro_series.get(series, series)
138
 
            # Hack the url and let the following do the final resolution.
139
 
            url = lp_url_template % dict(
140
 
                distro=distro,
141
 
                series=series,
142
 
                project=project)
143
 
            scheme, netloc, path, query, fragment = urlsplit(url)
144
 
        return url, path
145
 
 
146
 
    def _expand_user(self, path, url, lp_login):
147
 
        if path.startswith('~/'):
148
 
            if lp_login is None:
149
 
                raise errors.InvalidURL(path=url,
150
 
                    extra='Cannot resolve "~" to your username.'
151
 
                          ' See "bzr help launchpad-login"')
152
 
            path = '~' + lp_login + path[1:]
153
 
        return path
154
 
 
155
 
    def _resolve(self, url,
156
 
                 _request_factory=ResolveLaunchpadPathRequest,
157
 
                 _lp_login=None):
158
 
        """Resolve the base URL for this transport."""
159
 
        url, path = self._update_url_scheme(url)
 
77
                path=abspath, extra=fault.faultString)
 
78
 
 
79
        if 'launchpad' in debug.debug_flags:
 
80
            trace.mutter("resolve_lp_path(%r) == %r", path, result)
 
81
 
160
82
        if _lp_login is None:
161
83
            _lp_login = get_lp_login()
162
 
        path = path.strip('/')
163
 
        path = self._expand_user(path, url, _lp_login)
164
 
        if _lp_login is not None:
165
 
            result = self._resolve_locally(path, url, _request_factory)
166
 
            if 'launchpad' in debug.debug_flags:
167
 
                local_res = result
168
 
                result = self._resolve_via_xmlrpc(path, url, _request_factory)
169
 
                trace.note(gettext(
170
 
                    'resolution for {0}\n  local: {1}\n remote: {2}').format(
171
 
                           url, local_res['urls'], result['urls']))
172
 
        else:
173
 
            result = self._resolve_via_xmlrpc(path, url, _request_factory)
174
 
 
175
 
        if 'launchpad' in debug.debug_flags:
176
 
            trace.mutter("resolve_lp_path(%r) == %r", url, result)
177
 
 
178
 
        _warned_login = False
179
84
        for url in result['urls']:
180
85
            scheme, netloc, path, query, fragment = urlsplit(url)
181
 
            if self._requires_launchpad_login(scheme, netloc, path, query,
182
 
                                              fragment):
 
86
            if scheme == 'bzr+ssh' and (netloc.endswith('launchpad.net') or
 
87
                                        netloc.endswith('launchpad.dev')):
183
88
                # Only accept launchpad.net bzr+ssh URLs if we know
184
89
                # the user's Launchpad login:
185
 
                if _lp_login is not None:
186
 
                    break
187
90
                if _lp_login is None:
188
 
                    if not _warned_login:
189
 
                        trace.warning(
190
 
'You have not informed bzr of your Launchpad ID, and you must do this to\n'
191
 
'write to Launchpad or access private data.  See "bzr help launchpad-login".')
192
 
                        _warned_login = True
 
91
                    continue
 
92
                url = urlunsplit((scheme, '%s@%s' % (_lp_login, netloc),
 
93
                                  path, query, fragment))
 
94
                break
193
95
            else:
194
96
                # Use the URL if we can create a transport for it.
195
97
                try:
196
 
                    transport.get_transport(url)
 
98
                    get_transport(url)
197
99
                except (errors.PathError, errors.TransportError):
198
100
                    pass
199
101
                else:
200
102
                    break
201
103
        else:
202
 
            raise errors.InvalidURL(path=url, extra='no supported schemes')
 
104
            raise errors.InvalidURL(path=abspath,
 
105
                                    extra='no supported schemes')
203
106
        return url
204
107
 
 
108
    def _request_redirect(self, relpath):
 
109
        source = urlutils.join(self.base, relpath)
 
110
        # Split the source location into the branch location, and the
 
111
        # extra path components.
 
112
        pos = source.find('/.bzr/')
 
113
        if pos >= 0:
 
114
            branchpath = source[:pos]
 
115
            extra = source[pos:]
 
116
        else:
 
117
            branchpath = source
 
118
            extra = ''
 
119
        target = self._resolve(branchpath) + extra
 
120
        raise errors.RedirectRequested(
 
121
            source=source,
 
122
            target=target)
 
123
 
 
124
    def get(self, relpath):
 
125
        """See Transport.get()."""
 
126
        self._request_redirect(relpath)
 
127
 
 
128
    def mkdir(self, relpath, mode=None):
 
129
        """See Transport.mkdir()."""
 
130
        self._request_redirect(relpath)
 
131
 
205
132
 
206
133
def get_test_permutations():
207
134
    # Since this transport doesn't do anything once opened, it's not subjected