~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Martin Pool
  • Date: 2010-01-29 14:09:05 UTC
  • mto: This revision was merged to the branch mainline in revision 4992.
  • Revision ID: mbp@sourcefrog.net-20100129140905-2uiarb6p8di1ywsr
Correction to url

from review: https://code.edge.launchpad.net/~mbp/bzr/doc/+merge/18250

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2009 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
22
22
 
23
23
 
24
24
import os
25
 
import re
26
 
import urlparse
27
25
 
28
26
from bzrlib import (
29
 
    branch,
30
27
    config,
31
28
    errors,
32
29
    osutils,
33
 
    trace,
34
 
    transport,
35
30
    )
36
31
from bzrlib.plugins.launchpad.lp_registration import (
37
32
    InvalidLaunchpadInstance,
 
33
    NotLaunchpadBranch,
38
34
    )
39
35
 
40
36
try:
43
39
    raise errors.DependencyNotPresent('launchpadlib', e)
44
40
 
45
41
from launchpadlib.launchpad import (
 
42
    EDGE_SERVICE_ROOT,
46
43
    STAGING_SERVICE_ROOT,
47
44
    Launchpad,
48
45
    )
73
70
            installed_version, installed_version)
74
71
 
75
72
 
76
 
# The older versions of launchpadlib only provided service root constants for
77
 
# edge and staging, whilst newer versions drop edge. Therefore service root
78
 
# URIs for which we do not always have constants are derived from the staging
79
 
# one, which does always exist.
80
 
#
81
 
# It is necessary to derive, rather than use hardcoded URIs because
82
 
# launchpadlib <= 1.5.4 requires service root URIs that end in a path of
83
 
# /beta/, whilst launchpadlib >= 1.5.5 requires service root URIs with no path
84
 
# info.
85
 
#
86
 
# Once we have a hard dependency on launchpadlib >= 1.5.4 we can replace all of
87
 
# bzr's local knowledge of individual Launchpad instances with use of the
88
 
# launchpadlib.uris module.
89
73
LAUNCHPAD_API_URLS = {
90
 
    'production': STAGING_SERVICE_ROOT.replace('api.staging.launchpad.net',
91
 
        'api.launchpad.net'),
92
 
    'qastaging': STAGING_SERVICE_ROOT.replace('api.staging.launchpad.net',
93
 
        'api.qastaging.launchpad.net'),
 
74
    'production': 'https://api.launchpad.net/beta/',
 
75
    'edge': EDGE_SERVICE_ROOT,
94
76
    'staging': STAGING_SERVICE_ROOT,
95
 
    'dev': STAGING_SERVICE_ROOT.replace('api.staging.launchpad.net',
96
 
        'api.launchpad.dev'),
 
77
    'dev': 'https://api.launchpad.dev/beta/',
97
78
    }
98
79
 
99
80
 
100
81
def _get_api_url(service):
101
82
    """Return the root URL of the Launchpad API.
102
83
 
103
 
    e.g. For the 'staging' Launchpad service, this function returns
104
 
    launchpadlib.launchpad.STAGING_SERVICE_ROOT.
 
84
    e.g. For the 'edge' Launchpad service, this function returns
 
85
    launchpadlib.launchpad.EDGE_SERVICE_ROOT.
105
86
 
106
87
    :param service: A `LaunchpadService` object.
107
88
    :return: A URL as a string.
116
97
        raise InvalidLaunchpadInstance(lp_instance)
117
98
 
118
99
 
119
 
class NoLaunchpadBranch(errors.BzrError):
120
 
    _fmt = 'No launchpad branch could be found for branch "%(url)s".'
121
 
 
122
 
    def __init__(self, branch):
123
 
        errors.BzrError.__init__(self, branch=branch, url=branch.base)
124
 
 
125
 
 
126
100
def login(service, timeout=None, proxy_info=None):
127
101
    """Log in to the Launchpad API.
128
102
 
138
112
    return launchpad
139
113
 
140
114
 
141
 
class LaunchpadBranch(object):
142
 
    """Provide bzr and lp API access to a Launchpad branch."""
143
 
 
144
 
    def __init__(self, lp_branch, bzr_url, bzr_branch=None, check_update=True):
145
 
        """Constructor.
146
 
 
147
 
        :param lp_branch: The Launchpad branch.
148
 
        :param bzr_url: The URL of the Bazaar branch.
149
 
        :param bzr_branch: An instance of the Bazaar branch.
150
 
        """
151
 
        self.bzr_url = bzr_url
152
 
        self._bzr = bzr_branch
153
 
        self._push_bzr = None
154
 
        self._check_update = check_update
155
 
        self.lp = lp_branch
156
 
 
157
 
    @property
158
 
    def bzr(self):
159
 
        """Return the bzr branch for this branch."""
160
 
        if self._bzr is None:
161
 
            self._bzr = branch.Branch.open(self.bzr_url)
162
 
        return self._bzr
163
 
 
164
 
    @property
165
 
    def push_bzr(self):
166
 
        """Return the push branch for this branch."""
167
 
        if self._push_bzr is None:
168
 
            self._push_bzr = branch.Branch.open(self.lp.bzr_identity)
169
 
        return self._push_bzr
170
 
 
171
 
    @staticmethod
172
 
    def plausible_launchpad_url(url):
173
 
        """Is 'url' something that could conceivably be pushed to LP?
174
 
 
175
 
        :param url: A URL that may refer to a Launchpad branch.
176
 
        :return: A boolean.
177
 
        """
178
 
        if url is None:
179
 
            return False
180
 
        if url.startswith('lp:'):
181
 
            return True
182
 
        regex = re.compile('([a-z]*\+)*(bzr\+ssh|http)'
183
 
                           '://bazaar.*.launchpad.net')
184
 
        return bool(regex.match(url))
185
 
 
186
 
    @staticmethod
187
 
    def candidate_urls(bzr_branch):
188
 
        """Iterate through related URLs that might be Launchpad URLs.
189
 
 
190
 
        :param bzr_branch: A Bazaar branch to find URLs from.
191
 
        :return: a generator of URL strings.
192
 
        """
193
 
        url = bzr_branch.get_public_branch()
194
 
        if url is not None:
195
 
            yield url
196
 
        url = bzr_branch.get_push_location()
197
 
        if url is not None:
198
 
            yield url
199
 
        url = bzr_branch.get_parent()
200
 
        if url is not None:
201
 
            yield url
202
 
        yield bzr_branch.base
203
 
 
204
 
    @staticmethod
205
 
    def tweak_url(url, launchpad):
206
 
        """Adjust a URL to work with staging, if needed."""
207
 
        if str(launchpad._root_uri) == STAGING_SERVICE_ROOT:
208
 
            return url.replace('bazaar.launchpad.net',
209
 
                               'bazaar.staging.launchpad.net')
210
 
        elif str(launchpad._root_uri) == LAUNCHPAD_API_URLS['qastaging']:
211
 
            return url.replace('bazaar.launchpad.net',
212
 
                               'bazaar.qastaging.launchpad.net')
213
 
        return url
214
 
 
215
 
    @classmethod
216
 
    def from_bzr(cls, launchpad, bzr_branch, create_missing=True):
217
 
        """Find a Launchpad branch from a bzr branch."""
218
 
        check_update = True
219
 
        for url in cls.candidate_urls(bzr_branch):
220
 
            url = cls.tweak_url(url, launchpad)
221
 
            if not cls.plausible_launchpad_url(url):
222
 
                continue
223
 
            lp_branch = launchpad.branches.getByUrl(url=url)
224
 
            if lp_branch is not None:
225
 
                break
226
 
        else:
227
 
            if not create_missing:
228
 
                raise NoLaunchpadBranch(bzr_branch)
229
 
            lp_branch = cls.create_now(launchpad, bzr_branch)
230
 
            check_update = False
231
 
        return cls(lp_branch, bzr_branch.base, bzr_branch, check_update)
232
 
 
233
 
    @classmethod
234
 
    def create_now(cls, launchpad, bzr_branch):
235
 
        """Create a Bazaar branch on Launchpad for the supplied branch."""
236
 
        url = cls.tweak_url(bzr_branch.get_push_location(), launchpad)
237
 
        if not cls.plausible_launchpad_url(url):
238
 
            raise errors.BzrError('%s is not registered on Launchpad' %
239
 
                                  bzr_branch.base)
240
 
        bzr_branch.create_clone_on_transport(transport.get_transport(url))
 
115
def load_branch(launchpad, branch):
 
116
    """Return the launchpadlib Branch object corresponding to 'branch'.
 
117
 
 
118
    :param launchpad: The root `Launchpad` object from launchpadlib.
 
119
    :param branch: A `bzrlib.branch.Branch`.
 
120
    :raise NotLaunchpadBranch: If we cannot determine the Launchpad URL of
 
121
        `branch`.
 
122
    :return: A launchpadlib Branch object.
 
123
    """
 
124
    # XXX: This duplicates the "What are possible URLs for the branch that
 
125
    # Launchpad might recognize" logic found in cmd_lp_open.
 
126
 
 
127
    # XXX: This makes multiple roundtrips to Launchpad for what is
 
128
    # conceptually a single operation -- get me the branches that match these
 
129
    # URLs. Unfortunately, Launchpad's support for such operations is poor, so
 
130
    # we have to allow multiple roundtrips.
 
131
    for url in branch.get_public_branch(), branch.get_push_location():
241
132
        lp_branch = launchpad.branches.getByUrl(url=url)
242
 
        if lp_branch is None:
243
 
            raise errors.BzrError('%s is not registered on Launchpad' % url)
244
 
        return lp_branch
245
 
 
246
 
    def get_target(self):
247
 
        """Return the 'LaunchpadBranch' for the target of this one."""
248
 
        lp_branch = self.lp
249
 
        if lp_branch.project is not None:
250
 
            dev_focus = lp_branch.project.development_focus
251
 
            if dev_focus is None:
252
 
                raise errors.BzrError('%s has no development focus.' %
253
 
                                  lp_branch.bzr_identity)
254
 
            target = dev_focus.branch
255
 
            if target is None:
256
 
                raise errors.BzrError('development focus %s has no branch.' % dev_focus)
257
 
        elif lp_branch.sourcepackage is not None:
258
 
            target = lp_branch.sourcepackage.getBranch(pocket="Release")
259
 
            if target is None:
260
 
                raise errors.BzrError('source package %s has no branch.' %
261
 
                                      lp_branch.sourcepackage)
262
 
        else:
263
 
            raise errors.BzrError('%s has no associated product or source package.' %
264
 
                                  lp_branch.bzr_identity)
265
 
        return LaunchpadBranch(target, target.bzr_identity)
266
 
 
267
 
    def update_lp(self):
268
 
        """Update the Launchpad copy of this branch."""
269
 
        if not self._check_update:
270
 
            return
271
 
        self.bzr.lock_read()
272
 
        try:
273
 
            if self.lp.last_scanned_id is not None:
274
 
                if self.bzr.last_revision() == self.lp.last_scanned_id:
275
 
                    trace.note('%s is already up-to-date.' %
276
 
                               self.lp.bzr_identity)
277
 
                    return
278
 
                graph = self.bzr.repository.get_graph()
279
 
                if not graph.is_ancestor(self.lp.last_scanned_id,
280
 
                                         self.bzr.last_revision()):
281
 
                    raise errors.DivergedBranches(self.bzr, self.push_bzr)
282
 
                trace.note('Pushing to %s' % self.lp.bzr_identity)
283
 
            self.bzr.push(self.push_bzr)
284
 
        finally:
285
 
            self.bzr.unlock()
286
 
 
287
 
    def find_lca_tree(self, other):
288
 
        """Find the revision tree for the LCA of this branch and other.
289
 
 
290
 
        :param other: Another LaunchpadBranch
291
 
        :return: The RevisionTree of the LCA of this branch and other.
292
 
        """
293
 
        graph = self.bzr.repository.get_graph(other.bzr.repository)
294
 
        lca = graph.find_unique_lca(self.bzr.last_revision(),
295
 
                                    other.bzr.last_revision())
296
 
        return self.bzr.repository.revision_tree(lca)
297
 
 
298
 
 
299
 
def canonical_url(object):
300
 
    """Return the canonical URL for a branch."""
301
 
    scheme, netloc, path, params, query, fragment = urlparse.urlparse(
302
 
        str(object.self_link))
303
 
    path = '/'.join(path.split('/')[2:])
304
 
    netloc = netloc.replace('api.', 'code.')
305
 
    return urlparse.urlunparse((scheme, netloc, path, params, query,
306
 
                                fragment))
 
133
        if lp_branch:
 
134
            return lp_branch
 
135
    raise NotLaunchpadBranch(url)