~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: INADA Naoki
  • Date: 2011-05-05 09:15:34 UTC
  • mto: (5830.3.3 i18n-msgfmt)
  • mto: This revision was merged to the branch mainline in revision 5873.
  • Revision ID: songofacandy@gmail.com-20110505091534-7sv835xpofwrmpt4
Add update-pot command to Makefile and tools/bzrgettext script that
extracts help text from bzr commands.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009 Canonical Ltd
 
1
# Copyright (C) 2009, 2010 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
25
27
 
26
28
from bzrlib import (
 
29
    branch,
27
30
    config,
28
31
    errors,
29
32
    osutils,
 
33
    trace,
 
34
    transport,
30
35
    )
31
36
from bzrlib.plugins.launchpad.lp_registration import (
32
37
    InvalidLaunchpadInstance,
33
 
    NotLaunchpadBranch,
34
38
    )
35
39
 
36
40
try:
39
43
    raise errors.DependencyNotPresent('launchpadlib', e)
40
44
 
41
45
from launchpadlib.launchpad import (
42
 
    EDGE_SERVICE_ROOT,
43
46
    STAGING_SERVICE_ROOT,
44
47
    Launchpad,
45
48
    )
70
73
            installed_version, installed_version)
71
74
 
72
75
 
 
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.
73
89
LAUNCHPAD_API_URLS = {
74
 
    'production': 'https://api.launchpad.net/beta/',
75
 
    'edge': EDGE_SERVICE_ROOT,
 
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'),
76
94
    'staging': STAGING_SERVICE_ROOT,
77
 
    'dev': 'https://api.launchpad.dev/beta/',
 
95
    'dev': STAGING_SERVICE_ROOT.replace('api.staging.launchpad.net',
 
96
        'api.launchpad.dev'),
78
97
    }
79
98
 
80
99
 
81
100
def _get_api_url(service):
82
101
    """Return the root URL of the Launchpad API.
83
102
 
84
 
    e.g. For the 'edge' Launchpad service, this function returns
85
 
    launchpadlib.launchpad.EDGE_SERVICE_ROOT.
 
103
    e.g. For the 'staging' Launchpad service, this function returns
 
104
    launchpadlib.launchpad.STAGING_SERVICE_ROOT.
86
105
 
87
106
    :param service: A `LaunchpadService` object.
88
107
    :return: A URL as a string.
97
116
        raise InvalidLaunchpadInstance(lp_instance)
98
117
 
99
118
 
 
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
 
100
126
def login(service, timeout=None, proxy_info=None):
101
127
    """Log in to the Launchpad API.
102
128
 
112
138
    return launchpad
113
139
 
114
140
 
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():
 
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))
132
241
        lp_branch = launchpad.branches.getByUrl(url=url)
133
 
        if lp_branch:
134
 
            return lp_branch
135
 
    raise NotLaunchpadBranch(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))