~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: John Arbash Meinel
  • Date: 2010-01-05 04:30:07 UTC
  • mfrom: (4932 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4934.
  • Revision ID: john@arbash-meinel.com-20100105043007-ehgbldqd3q0gtyws
Merge bzr.dev, resolve conflicts.

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