/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
4797.32.2 by John Arbash Meinel
merge 2.1, resolving NEWS conflict.
1
# Copyright (C) 2009, 2010 Canonical Ltd
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
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
"""Tools for dealing with the Launchpad API."""
18
4505.6.11 by Jonathan Lange
Flag lp_api as a difficult import.
19
# Importing this module will be expensive, since it imports launchpadlib and
20
# its dependencies. However, our plan is to only load this module when it is
21
# needed by a command that uses it.
22
4505.6.18 by Jonathan Lange
Another review comment to make a note of.
23
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
24
import os
4969.2.7 by Aaron Bentley
Add lp-submit, get working.
25
import re
5546.2.1 by Aaron Bentley
Add lp-find-proposal.
26
import urlparse
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
27
28
from bzrlib import (
4969.2.7 by Aaron Bentley
Add lp-submit, get working.
29
    branch,
4505.6.24 by Jonathan Lange
Move cache directory to the Bazaar configuration directory.
30
    config,
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
31
    errors,
4505.6.16 by Jonathan Lange
Work on Windows, I think.
32
    osutils,
4969.2.11 by Aaron Bentley
Clean up imports.
33
    trace,
34
    transport,
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
35
    )
36
from bzrlib.plugins.launchpad.lp_registration import (
37
    InvalidLaunchpadInstance,
38
    NotLaunchpadBranch,
39
    )
40
4505.6.25 by Jonathan Lange
Add a test to check what happens if launchpadlib not available.
41
try:
4505.6.27 by Jonathan Lange
Add some tests to check for version compatibility. Drop tests for
42
    import launchpadlib
4505.6.25 by Jonathan Lange
Add a test to check what happens if launchpadlib not available.
43
except ImportError, e:
44
    raise errors.DependencyNotPresent('launchpadlib', e)
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
45
4505.6.27 by Jonathan Lange
Add some tests to check for version compatibility. Drop tests for
46
from launchpadlib.launchpad import (
47
    STAGING_SERVICE_ROOT,
48
    Launchpad,
49
    )
5615.2.3 by Jelmer Vernooij
Use getattr as suggested by John.
50
from launchpadlib import uris
4505.6.27 by Jonathan Lange
Add some tests to check for version compatibility. Drop tests for
51
5615.2.3 by Jelmer Vernooij
Use getattr as suggested by John.
52
LPNET_SERVICE_ROOT = getattr(uris, 'LPNET_SERVICE_ROOT',
53
                             'https://api.launchpad.net/beta/')
54
QASTAGING_SERVICE_ROOT = getattr(uris, 'QASTAGING_SERVICE_ROOT',
55
                                 'https://api.qastaging.launchpad.net/')
4505.6.27 by Jonathan Lange
Add some tests to check for version compatibility. Drop tests for
56
57
# Declare the minimum version of launchpadlib that we need in order to work.
58
# 1.5.1 is the version of launchpadlib packaged in Ubuntu 9.10, the most
59
# recent Ubuntu release at the time of writing.
60
MINIMUM_LAUNCHPADLIB_VERSION = (1, 5, 1)
61
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
62
4505.6.15 by Jonathan Lange
Baby steps: Move the cache directory stuff into a function.
63
def get_cache_directory():
64
    """Return the directory to cache launchpadlib objects in."""
4505.6.24 by Jonathan Lange
Move cache directory to the Bazaar configuration directory.
65
    return osutils.pathjoin(config.config_dir(), 'launchpad')
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
66
67
4505.6.27 by Jonathan Lange
Add some tests to check for version compatibility. Drop tests for
68
def parse_launchpadlib_version(version_number):
69
    """Parse a version number of the style used by launchpadlib."""
70
    return tuple(map(int, version_number.split('.')))
71
72
73
def check_launchpadlib_compatibility():
74
    """Raise an error if launchpadlib has the wrong version number."""
75
    installed_version = parse_launchpadlib_version(launchpadlib.__version__)
76
    if installed_version < MINIMUM_LAUNCHPADLIB_VERSION:
77
        raise errors.IncompatibleAPI(
78
            'launchpadlib', MINIMUM_LAUNCHPADLIB_VERSION,
79
            installed_version, installed_version)
80
81
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
82
LAUNCHPAD_API_URLS = {
5546.2.2 by Aaron Bentley
Switch lp-find-proposal to production API.
83
    'production': LPNET_SERVICE_ROOT,
5615.2.1 by Jelmer Vernooij
Support the 'qastaging' instance of Launchpad.
84
    'qastaging': QASTAGING_SERVICE_ROOT,
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
85
    'staging': STAGING_SERVICE_ROOT,
86
    'dev': 'https://api.launchpad.dev/beta/',
87
    }
88
89
90
def _get_api_url(service):
91
    """Return the root URL of the Launchpad API.
92
4797.76.5 by Vincent Ladeuil
Fix edge references in lp_api and more comments.
93
    e.g. For the 'staging' Launchpad service, this function returns
94
    launchpadlib.launchpad.STAGING_SERVICE_ROOT.
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
95
96
    :param service: A `LaunchpadService` object.
97
    :return: A URL as a string.
98
    """
99
    if service._lp_instance is None:
100
        lp_instance = service.DEFAULT_INSTANCE
101
    else:
102
        lp_instance = service._lp_instance
103
    try:
104
        return LAUNCHPAD_API_URLS[lp_instance]
105
    except KeyError:
106
        raise InvalidLaunchpadInstance(lp_instance)
107
108
5546.2.3 by Aaron Bentley
Tighten revno check, avoid creating branches on lp.
109
class NoLaunchpadBranch(errors.BzrError):
110
    _fmt = 'No launchpad branch could be found for branch "%(url)s".'
111
112
    def __init__(self, branch):
113
        errors.BzrError.__init__(self, branch=branch, url=branch.base)
114
115
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
116
def login(service, timeout=None, proxy_info=None):
117
    """Log in to the Launchpad API.
118
119
    :return: The root `Launchpad` object from launchpadlib.
120
    """
4505.6.19 by Jonathan Lange
Delete swathes of code because we can rely on a version of launchpadlib
121
    cache_directory = get_cache_directory()
122
    launchpad = Launchpad.login_with(
123
        'bzr', _get_api_url(service), cache_directory, timeout=timeout,
124
        proxy_info=proxy_info)
125
    # XXX: Work-around a minor security bug in launchpadlib 1.5.1, which would
126
    # create this directory with default umask.
127
    os.chmod(cache_directory, 0700)
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
128
    return launchpad
129
130
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
131
class LaunchpadBranch(object):
4969.2.10 by Aaron Bentley
Cleanup and docs.
132
    """Provide bzr and lp API access to a Launchpad branch."""
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
133
134
    def __init__(self, lp_branch, bzr_url, bzr_branch=None, check_update=True):
4969.2.10 by Aaron Bentley
Cleanup and docs.
135
        """Constructor.
136
137
        :param lp_branch: The Launchpad branch.
138
        :param bzr_url: The URL of the Bazaar branch.
139
        :param bzr_branch: An instance of the Bazaar branch.
140
        """
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
141
        self.bzr_url = bzr_url
142
        self._bzr = bzr_branch
143
        self._push_bzr = None
4969.2.14 by Aaron Bentley
Restore update functionality.
144
        self._check_update = check_update
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
145
        self.lp = lp_branch
146
147
    @property
148
    def bzr(self):
4969.2.10 by Aaron Bentley
Cleanup and docs.
149
        """Return the bzr branch for this branch."""
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
150
        if self._bzr is None:
151
            self._bzr = branch.Branch.open(self.bzr_url)
152
        return self._bzr
153
154
    @property
155
    def push_bzr(self):
4969.2.10 by Aaron Bentley
Cleanup and docs.
156
        """Return the push branch for this branch."""
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
157
        if self._push_bzr is None:
158
            self._push_bzr = branch.Branch.open(self.lp.bzr_identity)
159
        return self._push_bzr
160
161
    @staticmethod
162
    def plausible_launchpad_url(url):
4969.2.10 by Aaron Bentley
Cleanup and docs.
163
        """Is 'url' something that could conceivably be pushed to LP?
164
165
        :param url: A URL that may refer to a Launchpad branch.
166
        :return: A boolean.
167
        """
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
168
        if url is None:
169
            return False
170
        if url.startswith('lp:'):
171
            return True
172
        regex = re.compile('([a-z]*\+)*(bzr\+ssh|http)'
173
                           '://bazaar.*.launchpad.net')
174
        return bool(regex.match(url))
175
176
    @staticmethod
177
    def candidate_urls(bzr_branch):
4969.2.10 by Aaron Bentley
Cleanup and docs.
178
        """Iterate through related URLs that might be Launchpad URLs.
179
180
        :param bzr_branch: A Bazaar branch to find URLs from.
181
        :return: a generator of URL strings.
182
        """
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
183
        url = bzr_branch.get_public_branch()
184
        if url is not None:
185
            yield url
186
        url = bzr_branch.get_push_location()
187
        if url is not None:
188
            yield url
189
        yield bzr_branch.base
190
191
    @staticmethod
192
    def tweak_url(url, launchpad):
4969.2.10 by Aaron Bentley
Cleanup and docs.
193
        """Adjust a URL to work with staging, if needed."""
5615.2.1 by Jelmer Vernooij
Support the 'qastaging' instance of Launchpad.
194
        if str(launchpad._root_uri) == STAGING_SERVICE_ROOT:
195
            return url.replace('bazaar.launchpad.net',
196
                               'bazaar.staging.launchpad.net')
197
        elif str(launchpad._root_uri) == QASTAGING_SERVICE_ROOT:
198
            return url.replace('bazaar.launchpad.net',
199
                               'bazaar.qastaging.launchpad.net')
200
        return url
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
201
202
    @classmethod
5546.2.3 by Aaron Bentley
Tighten revno check, avoid creating branches on lp.
203
    def from_bzr(cls, launchpad, bzr_branch, create_missing=True):
4969.2.10 by Aaron Bentley
Cleanup and docs.
204
        """Find a Launchpad branch from a bzr branch."""
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
205
        check_update = True
206
        for url in cls.candidate_urls(bzr_branch):
207
            url = cls.tweak_url(url, launchpad)
208
            if not cls.plausible_launchpad_url(url):
209
                continue
210
            lp_branch = launchpad.branches.getByUrl(url=url)
211
            if lp_branch is not None:
212
                break
213
        else:
5546.2.3 by Aaron Bentley
Tighten revno check, avoid creating branches on lp.
214
            if not create_missing:
215
                raise NoLaunchpadBranch(bzr_branch)
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
216
            lp_branch = cls.create_now(launchpad, bzr_branch)
217
            check_update = False
218
        return cls(lp_branch, bzr_branch.base, bzr_branch, check_update)
219
220
    @classmethod
221
    def create_now(cls, launchpad, bzr_branch):
4969.2.10 by Aaron Bentley
Cleanup and docs.
222
        """Create a Bazaar branch on Launchpad for the supplied branch."""
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
223
        url = cls.tweak_url(bzr_branch.get_push_location(), launchpad)
224
        if not cls.plausible_launchpad_url(url):
225
            raise errors.BzrError('%s is not registered on Launchpad' %
226
                                  bzr_branch.base)
227
        bzr_branch.create_clone_on_transport(transport.get_transport(url))
228
        lp_branch = launchpad.branches.getByUrl(url=url)
229
        if lp_branch is None:
230
            raise errors.BzrError('%s is not registered on Launchpad' % url)
231
        return lp_branch
232
5616.1.1 by Jelmer Vernooij
Support 'bzr lp-propose' without an explicit target branch for packaging branches.
233
    def get_target(self):
234
        """Return the 'LaunchpadBranch' for the target of this one."""
4969.2.5 by Aaron Bentley
It makes more sense to get the dev focus from an existing Launchpad branch
235
        lp_branch = self.lp
5616.1.1 by Jelmer Vernooij
Support 'bzr lp-propose' without an explicit target branch for packaging branches.
236
        if lp_branch.project is not None:
237
            dev_focus = lp_branch.project.development_focus.branch
238
            if dev_focus is None:
239
                raise errors.BzrError('%s has no development focus.' %
240
                                  lp_branch.bzr_identity)
241
            target = dev_focus.branch
242
            if target is None:
243
                raise errors.BzrError('development focus %s has no branch.' % dev_focus)
244
        elif lp_branch.sourcepackage is not None:
245
            target = lp_branch.sourcepackage.getBranch(pocket="Release")
246
            if target is None:
247
                raise errors.BzrError('source package %s has no branch.' %
248
                                      lp_branch.sourcepackage)
249
        else:
250
            raise errors.BzrError('%s has no associated product or source package.' %
251
                                  lp_branch.bzr_identity)
252
        return LaunchpadBranch(target, target.bzr_identity)
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
253
254
    def update_lp(self):
4969.2.15 by Aaron Bentley
Update docs.
255
        """Update the Launchpad copy of this branch."""
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
256
        if not self._check_update:
257
            return
258
        self.bzr.lock_read()
259
        try:
260
            if self.lp.last_scanned_id is not None:
261
                if self.bzr.last_revision() == self.lp.last_scanned_id:
262
                    trace.note('%s is already up-to-date.' %
263
                               self.lp.bzr_identity)
264
                    return
265
                graph = self.bzr.repository.get_graph()
4969.2.18 by Aaron Bentley
Fix divergence check.
266
                if not graph.is_ancestor(self.lp.last_scanned_id,
267
                                         self.bzr.last_revision()):
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
268
                    raise errors.DivergedBranches(self.bzr, self.push_bzr)
269
                trace.note('Pushing to %s' % self.lp.bzr_identity)
270
            self.bzr.push(self.push_bzr)
271
        finally:
272
            self.bzr.unlock()
273
274
    def find_lca_tree(self, other):
4969.2.10 by Aaron Bentley
Cleanup and docs.
275
        """Find the revision tree for the LCA of this branch and other.
276
277
        :param other: Another LaunchpadBranch
278
        :return: The RevisionTree of the LCA of this branch and other.
279
        """
4969.2.3 by Aaron Bentley
Move LaunchpadBranch to lp_api. Change the interface so that it uses launchpad
280
        graph = self.bzr.repository.get_graph(other.bzr.repository)
281
        lca = graph.find_unique_lca(self.bzr.last_revision(),
282
                                    other.bzr.last_revision())
283
        return self.bzr.repository.revision_tree(lca)
284
285
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
286
def load_branch(launchpad, branch):
287
    """Return the launchpadlib Branch object corresponding to 'branch'.
288
289
    :param launchpad: The root `Launchpad` object from launchpadlib.
290
    :param branch: A `bzrlib.branch.Branch`.
291
    :raise NotLaunchpadBranch: If we cannot determine the Launchpad URL of
292
        `branch`.
293
    :return: A launchpadlib Branch object.
294
    """
4505.6.22 by Jonathan Lange
Remove the ugly _service hack by using getByUrl.
295
    # XXX: This duplicates the "What are possible URLs for the branch that
296
    # Launchpad might recognize" logic found in cmd_lp_open.
297
298
    # XXX: This makes multiple roundtrips to Launchpad for what is
299
    # conceptually a single operation -- get me the branches that match these
300
    # URLs. Unfortunately, Launchpad's support for such operations is poor, so
301
    # we have to allow multiple roundtrips.
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
302
    for url in branch.get_public_branch(), branch.get_push_location():
4505.6.22 by Jonathan Lange
Remove the ugly _service hack by using getByUrl.
303
        lp_branch = launchpad.branches.getByUrl(url=url)
304
        if lp_branch:
305
            return lp_branch
4505.6.6 by Jonathan Lange
Add a command to mirror Launchpad branches now.
306
    raise NotLaunchpadBranch(url)
5546.2.1 by Aaron Bentley
Add lp-find-proposal.
307
308
309
def canonical_url(object):
310
    """Return the canonical URL for a branch."""
311
    scheme, netloc, path, params, query, fragment = urlparse.urlparse(
312
        str(object.self_link))
313
    path = '/'.join(path.split('/')[2:])
314
    netloc = netloc.replace('api.', 'code.')
315
    return urlparse.urlunparse((scheme, netloc, path, params, query,
316
                                fragment))