~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-12-18 23:41:30 UTC
  • mfrom: (3099.3.7 graph_optimization)
  • Revision ID: pqm@pqm.ubuntu.com-20071218234130-061grgxsaf1g7bao
(jam) Implement ParentProviders.get_parent_map() and deprecate
        get_parents()

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):
 
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)
54
64
 
55
65
    def _requires_launchpad_login(self, scheme, netloc, path, query,
56
66
                                  fragment):
63
73
                and (netloc.endswith('launchpad.net')
64
74
                     or netloc.endswith('launchpad.dev')))
65
75
 
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)
 
76
    def _resolve(self, abspath,
 
77
                 _request_factory=ResolveLaunchpadPathRequest,
 
78
                 _lp_login=None):
 
79
        """Resolve the base URL for this transport."""
 
80
        path = urlsplit(abspath)[2].lstrip('/')
 
81
        # Perform an XMLRPC request to resolve the path
98
82
        resolve = _request_factory(path)
 
83
        service = LaunchpadService()
99
84
        try:
100
85
            result = resolve.submit(service)
101
86
        except xmlrpclib.Fault, fault:
102
87
            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)
 
88
                path=abspath, extra=fault.faultString)
 
89
 
 
90
        if 'launchpad' in debug.debug_flags:
 
91
            trace.mutter("resolve_lp_path(%r) == %r", path, result)
 
92
 
160
93
        if _lp_login is None:
161
94
            _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
95
        for url in result['urls']:
180
96
            scheme, netloc, path, query, fragment = urlsplit(url)
181
97
            if self._requires_launchpad_login(scheme, netloc, path, query,
182
98
                                              fragment):
183
99
                # Only accept launchpad.net bzr+ssh URLs if we know
184
100
                # the user's Launchpad login:
185
 
                if _lp_login is not None:
186
 
                    break
187
101
                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
 
102
                    continue
 
103
                url = urlunsplit((scheme, '%s@%s' % (_lp_login, netloc),
 
104
                                  path, query, fragment))
 
105
                break
193
106
            else:
194
107
                # Use the URL if we can create a transport for it.
195
108
                try:
196
 
                    transport.get_transport(url)
 
109
                    get_transport(url)
197
110
                except (errors.PathError, errors.TransportError):
198
111
                    pass
199
112
                else:
200
113
                    break
201
114
        else:
202
 
            raise errors.InvalidURL(path=url, extra='no supported schemes')
 
115
            raise errors.InvalidURL(path=abspath,
 
116
                                    extra='no supported schemes')
203
117
        return url
204
118
 
 
119
    def _request_redirect(self, relpath):
 
120
        source = urlutils.join(self.base, relpath)
 
121
        # Split the source location into the branch location, and the
 
122
        # extra path components.
 
123
        pos = source.find('/.bzr/')
 
124
        if pos >= 0:
 
125
            branchpath = source[:pos]
 
126
            extra = source[pos:]
 
127
        else:
 
128
            branchpath = source
 
129
            extra = ''
 
130
        target = self._resolve(branchpath) + extra
 
131
        raise errors.RedirectRequested(
 
132
            source=source,
 
133
            target=target)
 
134
 
 
135
    def get(self, relpath):
 
136
        """See Transport.get()."""
 
137
        self._request_redirect(relpath)
 
138
 
 
139
    def mkdir(self, relpath, mode=None):
 
140
        """See Transport.mkdir()."""
 
141
        self._request_redirect(relpath)
 
142
 
205
143
 
206
144
def get_test_permutations():
207
145
    # Since this transport doesn't do anything once opened, it's not subjected