~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Ian Clatworthy
  • Date: 2007-12-11 02:07:30 UTC
  • mto: (3119.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3120.
  • Revision ID: ian.clatworthy@internode.on.net-20071211020730-sdj4kj794dw0628e
make help topics more discoverable

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