~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Patch Queue Manager
  • Date: 2016-02-01 19:56:05 UTC
  • mfrom: (6615.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20160201195605-o7rl92wf6uyum3fk
(vila) Open trunk again as 2.8b1 (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2011 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""Launchpad.net integration plugin for Bazaar."""
 
17
"""Launchpad.net integration plugin for Bazaar.
 
18
 
 
19
This plugin provides facilities for working with Bazaar branches that are
 
20
hosted on Launchpad (http://launchpad.net).  It provides a directory service 
 
21
for referring to Launchpad branches using the "lp:" prefix.  For example,
 
22
lp:bzr refers to the Bazaar's main development branch and
 
23
lp:~username/project/branch-name can be used to refer to a specific branch.
 
24
 
 
25
This plugin provides a bug tracker so that "bzr commit --fixes lp:1234" will
 
26
record that revision as fixing Launchpad's bug 1234.
 
27
 
 
28
The plugin also provides the following commands:
 
29
 
 
30
    launchpad-login: Show or set the Launchpad user ID
 
31
    launchpad-open: Open a Launchpad branch page in your web browser
 
32
    lp-propose-merge: Propose merging a branch on Launchpad
 
33
    register-branch: Register a branch with launchpad.net
 
34
    launchpad-mirror: Ask Launchpad to mirror a branch now
 
35
 
 
36
"""
 
37
 
 
38
from __future__ import absolute_import
18
39
 
19
40
# The XMLRPC server address can be overridden by setting the environment
20
41
# variable $BZR_LP_XMLRPC_URL
21
42
 
22
 
# see http://bazaar-vcs.org/Specs/BranchRegistrationTool
23
 
 
24
 
# Since we are a built-in plugin we share the bzrlib version
25
 
from bzrlib import version_info
26
 
 
27
 
from bzrlib.lazy_import import lazy_import
28
 
lazy_import(globals(), """
 
43
# see http://wiki.bazaar.canonical.com/Specs/BranchRegistrationTool
 
44
 
29
45
from bzrlib import (
30
46
    branch as _mod_branch,
 
47
    config as _mod_config,
 
48
    lazy_regex,
 
49
    # Since we are a built-in plugin we share the bzrlib version
31
50
    trace,
 
51
    version_info,
32
52
    )
33
 
""")
34
 
 
35
 
from bzrlib import bzrdir
36
53
from bzrlib.commands import (
37
 
        Command,
38
 
        register_command,
39
 
)
 
54
    plugin_cmds,
 
55
    )
40
56
from bzrlib.directory_service import directories
41
 
from bzrlib.errors import (
42
 
    BzrCommandError,
43
 
    DependencyNotPresent,
44
 
    InvalidURL,
45
 
    NoPublicBranch,
46
 
    NotBranchError,
47
 
    )
48
57
from bzrlib.help_topics import topic_registry
49
 
from bzrlib.option import (
50
 
        Option,
51
 
        ListOption,
52
 
)
53
 
 
54
 
 
55
 
class cmd_register_branch(Command):
56
 
    __doc__ = """Register a branch with launchpad.net.
57
 
 
58
 
    This command lists a bzr branch in the directory of branches on
59
 
    launchpad.net.  Registration allows the branch to be associated with
60
 
    bugs or specifications.
61
 
 
62
 
    Before using this command you must register the project to which the
63
 
    branch belongs, and create an account for yourself on launchpad.net.
64
 
 
65
 
    arguments:
66
 
        public_url: The publicly visible url for the branch to register.
67
 
                    This must be an http or https url (which Launchpad can read
68
 
                    from to access the branch). Local file urls, SFTP urls, and
69
 
                    bzr+ssh urls will not work.
70
 
                    If no public_url is provided, bzr will use the configured
71
 
                    public_url if there is one for the current branch, and
72
 
                    otherwise error.
73
 
 
74
 
    example:
75
 
        bzr register-branch http://foo.com/bzr/fooproject.mine \\
76
 
                --project fooproject
77
 
    """
78
 
    takes_args = ['public_url?']
79
 
    takes_options = [
80
 
         Option('project',
81
 
                'Launchpad project short name to associate with the branch.',
82
 
                unicode),
83
 
         Option('product',
84
 
                'Launchpad product short name to associate with the branch.', 
85
 
                unicode,
86
 
                hidden=True),
87
 
         Option('branch-name',
88
 
                'Short name for the branch; '
89
 
                'by default taken from the last component of the url.',
90
 
                unicode),
91
 
         Option('branch-title',
92
 
                'One-sentence description of the branch.',
93
 
                unicode),
94
 
         Option('branch-description',
95
 
                'Longer description of the purpose or contents of the branch.',
96
 
                unicode),
97
 
         Option('author',
98
 
                "Branch author's email address, if not yourself.",
99
 
                unicode),
100
 
         Option('link-bug',
101
 
                'The bug this branch fixes.',
102
 
                int),
103
 
         Option('dry-run',
104
 
                'Prepare the request but don\'t actually send it.')
105
 
        ]
106
 
 
107
 
 
108
 
    def run(self,
109
 
            public_url=None,
110
 
            project='',
111
 
            product=None,
112
 
            branch_name='',
113
 
            branch_title='',
114
 
            branch_description='',
115
 
            author='',
116
 
            link_bug=None,
117
 
            dry_run=False):
118
 
        from bzrlib.plugins.launchpad.lp_registration import (
119
 
            BranchRegistrationRequest, BranchBugLinkRequest,
120
 
            DryRunLaunchpadService, LaunchpadService)
121
 
        if public_url is None:
122
 
            try:
123
 
                b = _mod_branch.Branch.open_containing('.')[0]
124
 
            except NotBranchError:
125
 
                raise BzrCommandError('register-branch requires a public '
126
 
                    'branch url - see bzr help register-branch.')
127
 
            public_url = b.get_public_branch()
128
 
            if public_url is None:
129
 
                raise NoPublicBranch(b)
130
 
        if product is not None:
131
 
            project = product
132
 
            trace.note('--product is deprecated; please use --project.')
133
 
 
134
 
 
135
 
        rego = BranchRegistrationRequest(branch_url=public_url,
136
 
                                         branch_name=branch_name,
137
 
                                         branch_title=branch_title,
138
 
                                         branch_description=branch_description,
139
 
                                         product_name=project,
140
 
                                         author_email=author,
141
 
                                         )
142
 
        linko = BranchBugLinkRequest(branch_url=public_url,
143
 
                                     bug_id=link_bug)
144
 
        if not dry_run:
145
 
            service = LaunchpadService()
146
 
            # This gives back the xmlrpc url that can be used for future
147
 
            # operations on the branch.  It's not so useful to print to the
148
 
            # user since they can't do anything with it from a web browser; it
149
 
            # might be nice for the server to tell us about an html url as
150
 
            # well.
151
 
        else:
152
 
            # Run on service entirely in memory
153
 
            service = DryRunLaunchpadService()
154
 
        service.gather_user_credentials()
155
 
        rego.submit(service)
156
 
        if link_bug:
157
 
            linko.submit(service)
158
 
        print 'Branch registered.'
159
 
 
160
 
register_command(cmd_register_branch)
161
 
 
162
 
 
163
 
class cmd_launchpad_open(Command):
164
 
    __doc__ = """Open a Launchpad branch page in your web browser."""
165
 
 
166
 
    aliases = ['lp-open']
167
 
    takes_options = [
168
 
        Option('dry-run',
169
 
               'Do not actually open the browser. Just say the URL we would '
170
 
               'use.'),
171
 
        ]
172
 
    takes_args = ['location?']
173
 
 
174
 
    def _possible_locations(self, location):
175
 
        """Yield possible external locations for the branch at 'location'."""
176
 
        yield location
177
 
        try:
178
 
            branch = _mod_branch.Branch.open_containing(location)[0]
179
 
        except NotBranchError:
180
 
            return
181
 
        branch_url = branch.get_public_branch()
182
 
        if branch_url is not None:
183
 
            yield branch_url
184
 
        branch_url = branch.get_push_location()
185
 
        if branch_url is not None:
186
 
            yield branch_url
187
 
 
188
 
    def _get_web_url(self, service, location):
189
 
        from bzrlib.plugins.launchpad.lp_registration import (
190
 
            NotLaunchpadBranch)
191
 
        for branch_url in self._possible_locations(location):
192
 
            try:
193
 
                return service.get_web_url_from_branch_url(branch_url)
194
 
            except (NotLaunchpadBranch, InvalidURL):
195
 
                pass
196
 
        raise NotLaunchpadBranch(branch_url)
197
 
 
198
 
    def run(self, location=None, dry_run=False):
199
 
        from bzrlib.plugins.launchpad.lp_registration import (
200
 
            LaunchpadService)
201
 
        if location is None:
202
 
            location = u'.'
203
 
        web_url = self._get_web_url(LaunchpadService(), location)
204
 
        trace.note('Opening %s in web browser' % web_url)
205
 
        if not dry_run:
206
 
            import webbrowser   # this import should not be lazy
207
 
                                # otherwise bzr.exe lacks this module
208
 
            webbrowser.open(web_url)
209
 
 
210
 
register_command(cmd_launchpad_open)
211
 
 
212
 
 
213
 
class cmd_launchpad_login(Command):
214
 
    __doc__ = """Show or set the Launchpad user ID.
215
 
 
216
 
    When communicating with Launchpad, some commands need to know your
217
 
    Launchpad user ID.  This command can be used to set or show the
218
 
    user ID that Bazaar will use for such communication.
219
 
 
220
 
    :Examples:
221
 
      Show the Launchpad ID of the current user::
222
 
 
223
 
          bzr launchpad-login
224
 
 
225
 
      Set the Launchpad ID of the current user to 'bob'::
226
 
 
227
 
          bzr launchpad-login bob
228
 
    """
229
 
    aliases = ['lp-login']
230
 
    takes_args = ['name?']
231
 
    takes_options = [
232
 
        'verbose',
233
 
        Option('no-check',
234
 
               "Don't check that the user name is valid."),
235
 
        ]
236
 
 
237
 
    def run(self, name=None, no_check=False, verbose=False):
238
 
        # This is totally separate from any launchpadlib login system.
239
 
        from bzrlib.plugins.launchpad import account
240
 
        check_account = not no_check
241
 
 
242
 
        if name is None:
243
 
            username = account.get_lp_login()
244
 
            if username:
245
 
                if check_account:
246
 
                    account.check_lp_login(username)
247
 
                    if verbose:
248
 
                        self.outf.write(
249
 
                            "Launchpad user ID exists and has SSH keys.\n")
250
 
                self.outf.write(username + '\n')
251
 
            else:
252
 
                self.outf.write('No Launchpad user ID configured.\n')
253
 
                return 1
254
 
        else:
255
 
            name = name.lower()
256
 
            if check_account:
257
 
                account.check_lp_login(name)
258
 
                if verbose:
259
 
                    self.outf.write(
260
 
                        "Launchpad user ID exists and has SSH keys.\n")
261
 
            account.set_lp_login(name)
262
 
            if verbose:
263
 
                self.outf.write("Launchpad user ID set to '%s'.\n" % (name,))
264
 
 
265
 
register_command(cmd_launchpad_login)
266
 
 
267
 
 
268
 
# XXX: cmd_launchpad_mirror is untested
269
 
class cmd_launchpad_mirror(Command):
270
 
    __doc__ = """Ask Launchpad to mirror a branch now."""
271
 
 
272
 
    aliases = ['lp-mirror']
273
 
    takes_args = ['location?']
274
 
 
275
 
    def run(self, location='.'):
276
 
        from bzrlib.plugins.launchpad import lp_api
277
 
        from bzrlib.plugins.launchpad.lp_registration import LaunchpadService
278
 
        branch = _mod_branch.Branch.open(location)
279
 
        service = LaunchpadService()
280
 
        launchpad = lp_api.login(service)
281
 
        lp_branch = lp_api.load_branch(launchpad, branch)
282
 
        lp_branch.requestMirror()
283
 
 
284
 
 
285
 
register_command(cmd_launchpad_mirror)
286
 
 
287
 
 
288
 
class cmd_lp_propose_merge(Command):
289
 
    __doc__ = """Propose merging a branch on Launchpad.
290
 
 
291
 
    This will open your usual editor to provide the initial comment.  When it
292
 
    has created the proposal, it will open it in your default web browser.
293
 
 
294
 
    The branch will be proposed to merge into SUBMIT_BRANCH.  If SUBMIT_BRANCH
295
 
    is not supplied, the remembered submit branch will be used.  If no submit
296
 
    branch is remembered, the development focus will be used.
297
 
 
298
 
    By default, the SUBMIT_BRANCH's review team will be requested to review
299
 
    the merge proposal.  This can be overriden by specifying --review (-R).
300
 
    The parameter the launchpad account name of the desired reviewer.  This
301
 
    may optionally be followed by '=' and the review type.  For example:
302
 
 
303
 
      bzr lp-propose-merge --review jrandom --review review-team=qa
304
 
 
305
 
    This will propose a merge,  request "jrandom" to perform a review of
306
 
    unspecified type, and request "review-team" to perform a "qa" review.
307
 
    """
308
 
 
309
 
    takes_options = [Option('staging',
310
 
                            help='Propose the merge on staging.'),
311
 
                     Option('message', short_name='m', type=unicode,
312
 
                            help='Commit message.'),
313
 
                     Option('approve',
314
 
                            help='Mark the proposal as approved immediately.'),
315
 
                     ListOption('review', short_name='R', type=unicode,
316
 
                            help='Requested reviewer and optional type.')]
317
 
 
318
 
    takes_args = ['submit_branch?']
319
 
 
320
 
    aliases = ['lp-submit', 'lp-propose']
321
 
 
322
 
    def run(self, submit_branch=None, review=None, staging=False,
323
 
            message=None, approve=False):
324
 
        from bzrlib.plugins.launchpad import lp_propose
325
 
        tree, branch, relpath = bzrdir.BzrDir.open_containing_tree_or_branch(
326
 
            '.')
327
 
        if review is None:
328
 
            reviews = None
329
 
        else:
330
 
            reviews = []
331
 
            for review in review:
332
 
                if '=' in review:
333
 
                    reviews.append(review.split('=', 2))
334
 
                else:
335
 
                    reviews.append((review, ''))
336
 
            if submit_branch is None:
337
 
                submit_branch = branch.get_submit_branch()
338
 
        if submit_branch is None:
339
 
            target = None
340
 
        else:
341
 
            target = _mod_branch.Branch.open(submit_branch)
342
 
        proposer = lp_propose.Proposer(tree, branch, target, message,
343
 
                                       reviews, staging, approve=approve)
344
 
        proposer.check_proposal()
345
 
        proposer.create_proposal()
346
 
 
347
 
 
348
 
register_command(cmd_lp_propose_merge)
 
58
 
 
59
for klsname, aliases in [
 
60
    ("cmd_register_branch", []),
 
61
    ("cmd_launchpad_open", ["lp-open"]),
 
62
    ("cmd_launchpad_login", ["lp-login"]),
 
63
    ("cmd_launchpad_mirror", ["lp-mirror"]),
 
64
    ("cmd_lp_propose_merge", ["lp-submit", "lp-propose"]),
 
65
    ("cmd_lp_find_proposal", [])]:
 
66
    plugin_cmds.register_lazy(klsname, aliases,
 
67
        "bzrlib.plugins.launchpad.cmds")
349
68
 
350
69
 
351
70
def _register_directory():
352
71
    directories.register_lazy('lp:', 'bzrlib.plugins.launchpad.lp_directory',
353
72
                              'LaunchpadDirectory',
354
73
                              'Launchpad-based directory service',)
 
74
    directories.register_lazy(
 
75
        'debianlp:', 'bzrlib.plugins.launchpad.lp_directory',
 
76
        'LaunchpadDirectory',
 
77
        'debianlp: shortcut')
 
78
    directories.register_lazy(
 
79
        'ubuntu:', 'bzrlib.plugins.launchpad.lp_directory',
 
80
        'LaunchpadDirectory',
 
81
        'ubuntu: shortcut')
 
82
 
355
83
_register_directory()
356
84
 
 
85
# This is kept in __init__ so that we don't load lp_api_lite unless the branch
 
86
# actually matches. That way we can avoid importing extra dependencies like
 
87
# json.
 
88
_package_branch = lazy_regex.lazy_compile(
 
89
    r'bazaar.launchpad.net.*?/'
 
90
    r'(?P<user>~[^/]+/)?(?P<archive>ubuntu|debian)/(?P<series>[^/]+/)?'
 
91
    r'(?P<project>[^/]+)(?P<branch>/[^/]+)?'
 
92
    )
 
93
 
 
94
def _get_package_branch_info(url):
 
95
    """Determine the packaging information for this URL.
 
96
 
 
97
    :return: If this isn't a packaging branch, return None. If it is, return
 
98
        (archive, series, project)
 
99
    """
 
100
    if url is None:
 
101
        return None
 
102
    m = _package_branch.search(url)
 
103
    if m is None:
 
104
        return None
 
105
    archive, series, project, user = m.group('archive', 'series',
 
106
                                             'project', 'user')
 
107
    if series is not None:
 
108
        # series is optional, so the regex includes the extra '/', we don't
 
109
        # want to send that on (it causes Internal Server Errors.)
 
110
        series = series.strip('/')
 
111
    if user is not None:
 
112
        user = user.strip('~/')
 
113
        if user != 'ubuntu-branches':
 
114
            return None
 
115
    return archive, series, project
 
116
 
 
117
 
 
118
def _check_is_up_to_date(the_branch):
 
119
    info = _get_package_branch_info(the_branch.base)
 
120
    if info is None:
 
121
        return
 
122
    c = the_branch.get_config_stack()
 
123
    verbosity = c.get('launchpad.packaging_verbosity')
 
124
    if not verbosity:
 
125
        trace.mutter('not checking %s because verbosity is turned off'
 
126
                     % (the_branch.base,))
 
127
        return
 
128
    archive, series, project = info
 
129
    from bzrlib.plugins.launchpad import lp_api_lite
 
130
    latest_pub = lp_api_lite.LatestPublication(archive, series, project)
 
131
    lp_api_lite.report_freshness(the_branch, verbosity, latest_pub)
 
132
 
 
133
 
 
134
def _register_hooks():
 
135
    _mod_branch.Branch.hooks.install_named_hook('open',
 
136
        _check_is_up_to_date, 'package-branch-up-to-date')
 
137
 
 
138
 
 
139
_register_hooks()
357
140
 
358
141
def load_tests(basic_tests, module, loader):
359
142
    testmod_names = [
360
143
        'test_account',
361
144
        'test_register',
362
145
        'test_lp_api',
 
146
        'test_lp_api_lite',
363
147
        'test_lp_directory',
364
148
        'test_lp_login',
365
149
        'test_lp_open',
403
187
topic_registry.register('launchpad',
404
188
    _launchpad_help,
405
189
    'Using Bazaar with Launchpad.net')
 
190
 
 
191
_mod_config.option_registry.register(
 
192
    _mod_config.Option('launchpad.packaging_verbosity', default=True,
 
193
          from_unicode=_mod_config.bool_from_store,
 
194
          help="""\
 
195
Whether to warn if a UDD package import branch is accessed that is out of date.
 
196
 
 
197
Setting this option to 'off' will disable verbosity.
 
198
"""))
 
199
_mod_config.option_registry.register(
 
200
    _mod_config.Option('launchpad_username', default=None,
 
201
        help="The username to login with when conneting to Launchpad."))