~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: 2015-12-17 18:39:00 UTC
  • mfrom: (6606.1.2 fix-float)
  • Revision ID: pqm@pqm.ubuntu.com-20151217183900-0719du2uv1kwu3lc
(vila) Inline testtools private method to fix an issue in xenial (the
 private implementation has changed in an backward incompatible way).
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
 
36
36
"""
37
37
 
 
38
from __future__ import absolute_import
 
39
 
38
40
# The XMLRPC server address can be overridden by setting the environment
39
41
# variable $BZR_LP_XMLRPC_URL
40
42
 
41
43
# see http://wiki.bazaar.canonical.com/Specs/BranchRegistrationTool
42
44
 
43
 
from bzrlib.lazy_import import lazy_import
44
 
lazy_import(globals(), """
45
 
from bzrlib import (
46
 
    ui,
47
 
    trace,
48
 
    )
49
 
from bzrlib.i18n import gettext
50
 
""")
51
 
 
52
45
from bzrlib import (
53
46
    branch as _mod_branch,
54
 
    bzrdir,
 
47
    config as _mod_config,
55
48
    lazy_regex,
56
49
    # Since we are a built-in plugin we share the bzrlib version
 
50
    trace,
57
51
    version_info,
58
52
    )
59
53
from bzrlib.commands import (
60
 
    Command,
61
 
    register_command,
 
54
    plugin_cmds,
62
55
    )
63
56
from bzrlib.directory_service import directories
64
 
from bzrlib.errors import (
65
 
    BzrCommandError,
66
 
    InvalidRevisionSpec,
67
 
    InvalidURL,
68
 
    NoPublicBranch,
69
 
    NotBranchError,
70
 
    )
71
57
from bzrlib.help_topics import topic_registry
72
 
from bzrlib.option import (
73
 
        Option,
74
 
        ListOption,
75
 
)
76
 
 
77
 
 
78
 
class cmd_register_branch(Command):
79
 
    __doc__ = """Register a branch with launchpad.net.
80
 
 
81
 
    This command lists a bzr branch in the directory of branches on
82
 
    launchpad.net.  Registration allows the branch to be associated with
83
 
    bugs or specifications.
84
 
 
85
 
    Before using this command you must register the project to which the
86
 
    branch belongs, and create an account for yourself on launchpad.net.
87
 
 
88
 
    arguments:
89
 
        public_url: The publicly visible url for the branch to register.
90
 
                    This must be an http or https url (which Launchpad can read
91
 
                    from to access the branch). Local file urls, SFTP urls, and
92
 
                    bzr+ssh urls will not work.
93
 
                    If no public_url is provided, bzr will use the configured
94
 
                    public_url if there is one for the current branch, and
95
 
                    otherwise error.
96
 
 
97
 
    example:
98
 
        bzr register-branch http://foo.com/bzr/fooproject.mine \\
99
 
                --project fooproject
100
 
    """
101
 
    takes_args = ['public_url?']
102
 
    takes_options = [
103
 
         Option('project',
104
 
                'Launchpad project short name to associate with the branch.',
105
 
                unicode),
106
 
         Option('product',
107
 
                'Launchpad product short name to associate with the branch.',
108
 
                unicode,
109
 
                hidden=True),
110
 
         Option('branch-name',
111
 
                'Short name for the branch; '
112
 
                'by default taken from the last component of the url.',
113
 
                unicode),
114
 
         Option('branch-title',
115
 
                'One-sentence description of the branch.',
116
 
                unicode),
117
 
         Option('branch-description',
118
 
                'Longer description of the purpose or contents of the branch.',
119
 
                unicode),
120
 
         Option('author',
121
 
                "Branch author's email address, if not yourself.",
122
 
                unicode),
123
 
         Option('link-bug',
124
 
                'The bug this branch fixes.',
125
 
                int),
126
 
         Option('dry-run',
127
 
                'Prepare the request but don\'t actually send it.')
128
 
        ]
129
 
 
130
 
 
131
 
    def run(self,
132
 
            public_url=None,
133
 
            project='',
134
 
            product=None,
135
 
            branch_name='',
136
 
            branch_title='',
137
 
            branch_description='',
138
 
            author='',
139
 
            link_bug=None,
140
 
            dry_run=False):
141
 
        from bzrlib.plugins.launchpad.lp_registration import (
142
 
            BranchRegistrationRequest, BranchBugLinkRequest,
143
 
            DryRunLaunchpadService, LaunchpadService)
144
 
        if public_url is None:
145
 
            try:
146
 
                b = _mod_branch.Branch.open_containing('.')[0]
147
 
            except NotBranchError:
148
 
                raise BzrCommandError(gettext(
149
 
                            'register-branch requires a public '
150
 
                            'branch url - see bzr help register-branch.'))
151
 
            public_url = b.get_public_branch()
152
 
            if public_url is None:
153
 
                raise NoPublicBranch(b)
154
 
        if product is not None:
155
 
            project = product
156
 
            trace.note(gettext(
157
 
                '--product is deprecated; please use --project.'))
158
 
 
159
 
 
160
 
        rego = BranchRegistrationRequest(branch_url=public_url,
161
 
                                         branch_name=branch_name,
162
 
                                         branch_title=branch_title,
163
 
                                         branch_description=branch_description,
164
 
                                         product_name=project,
165
 
                                         author_email=author,
166
 
                                         )
167
 
        linko = BranchBugLinkRequest(branch_url=public_url,
168
 
                                     bug_id=link_bug)
169
 
        if not dry_run:
170
 
            service = LaunchpadService()
171
 
            # This gives back the xmlrpc url that can be used for future
172
 
            # operations on the branch.  It's not so useful to print to the
173
 
            # user since they can't do anything with it from a web browser; it
174
 
            # might be nice for the server to tell us about an html url as
175
 
            # well.
176
 
        else:
177
 
            # Run on service entirely in memory
178
 
            service = DryRunLaunchpadService()
179
 
        service.gather_user_credentials()
180
 
        rego.submit(service)
181
 
        if link_bug:
182
 
            linko.submit(service)
183
 
        print 'Branch registered.'
184
 
 
185
 
register_command(cmd_register_branch)
186
 
 
187
 
 
188
 
class cmd_launchpad_open(Command):
189
 
    __doc__ = """Open a Launchpad branch page in your web browser."""
190
 
 
191
 
    aliases = ['lp-open']
192
 
    takes_options = [
193
 
        Option('dry-run',
194
 
               'Do not actually open the browser. Just say the URL we would '
195
 
               'use.'),
196
 
        ]
197
 
    takes_args = ['location?']
198
 
 
199
 
    def _possible_locations(self, location):
200
 
        """Yield possible external locations for the branch at 'location'."""
201
 
        yield location
202
 
        try:
203
 
            branch = _mod_branch.Branch.open_containing(location)[0]
204
 
        except NotBranchError:
205
 
            return
206
 
        branch_url = branch.get_public_branch()
207
 
        if branch_url is not None:
208
 
            yield branch_url
209
 
        branch_url = branch.get_push_location()
210
 
        if branch_url is not None:
211
 
            yield branch_url
212
 
 
213
 
    def _get_web_url(self, service, location):
214
 
        from bzrlib.plugins.launchpad.lp_registration import (
215
 
            NotLaunchpadBranch)
216
 
        for branch_url in self._possible_locations(location):
217
 
            try:
218
 
                return service.get_web_url_from_branch_url(branch_url)
219
 
            except (NotLaunchpadBranch, InvalidURL):
220
 
                pass
221
 
        raise NotLaunchpadBranch(branch_url)
222
 
 
223
 
    def run(self, location=None, dry_run=False):
224
 
        from bzrlib.plugins.launchpad.lp_registration import (
225
 
            LaunchpadService)
226
 
        if location is None:
227
 
            location = u'.'
228
 
        web_url = self._get_web_url(LaunchpadService(), location)
229
 
        trace.note(gettext('Opening %s in web browser') % web_url)
230
 
        if not dry_run:
231
 
            import webbrowser   # this import should not be lazy
232
 
                                # otherwise bzr.exe lacks this module
233
 
            webbrowser.open(web_url)
234
 
 
235
 
register_command(cmd_launchpad_open)
236
 
 
237
 
 
238
 
class cmd_launchpad_login(Command):
239
 
    __doc__ = """Show or set the Launchpad user ID.
240
 
 
241
 
    When communicating with Launchpad, some commands need to know your
242
 
    Launchpad user ID.  This command can be used to set or show the
243
 
    user ID that Bazaar will use for such communication.
244
 
 
245
 
    :Examples:
246
 
      Show the Launchpad ID of the current user::
247
 
 
248
 
          bzr launchpad-login
249
 
 
250
 
      Set the Launchpad ID of the current user to 'bob'::
251
 
 
252
 
          bzr launchpad-login bob
253
 
    """
254
 
    aliases = ['lp-login']
255
 
    takes_args = ['name?']
256
 
    takes_options = [
257
 
        'verbose',
258
 
        Option('no-check',
259
 
               "Don't check that the user name is valid."),
260
 
        ]
261
 
 
262
 
    def run(self, name=None, no_check=False, verbose=False):
263
 
        # This is totally separate from any launchpadlib login system.
264
 
        from bzrlib.plugins.launchpad import account
265
 
        check_account = not no_check
266
 
 
267
 
        if name is None:
268
 
            username = account.get_lp_login()
269
 
            if username:
270
 
                if check_account:
271
 
                    account.check_lp_login(username)
272
 
                    if verbose:
273
 
                        self.outf.write(gettext(
274
 
                            "Launchpad user ID exists and has SSH keys.\n"))
275
 
                self.outf.write(username + '\n')
276
 
            else:
277
 
                self.outf.write(gettext('No Launchpad user ID configured.\n'))
278
 
                return 1
279
 
        else:
280
 
            name = name.lower()
281
 
            if check_account:
282
 
                account.check_lp_login(name)
283
 
                if verbose:
284
 
                    self.outf.write(gettext(
285
 
                        "Launchpad user ID exists and has SSH keys.\n"))
286
 
            account.set_lp_login(name)
287
 
            if verbose:
288
 
                self.outf.write(gettext("Launchpad user ID set to '%s'.\n") %
289
 
                                                                        (name,))
290
 
 
291
 
register_command(cmd_launchpad_login)
292
 
 
293
 
 
294
 
# XXX: cmd_launchpad_mirror is untested
295
 
class cmd_launchpad_mirror(Command):
296
 
    __doc__ = """Ask Launchpad to mirror a branch now."""
297
 
 
298
 
    aliases = ['lp-mirror']
299
 
    takes_args = ['location?']
300
 
 
301
 
    def run(self, location='.'):
302
 
        from bzrlib.plugins.launchpad import lp_api
303
 
        from bzrlib.plugins.launchpad.lp_registration import LaunchpadService
304
 
        branch, _ = _mod_branch.Branch.open_containing(location)
305
 
        service = LaunchpadService()
306
 
        launchpad = lp_api.login(service)
307
 
        lp_branch = lp_api.LaunchpadBranch.from_bzr(launchpad, branch,
308
 
                create_missing=False)
309
 
        lp_branch.lp.requestMirror()
310
 
 
311
 
 
312
 
register_command(cmd_launchpad_mirror)
313
 
 
314
 
 
315
 
class cmd_lp_propose_merge(Command):
316
 
    __doc__ = """Propose merging a branch on Launchpad.
317
 
 
318
 
    This will open your usual editor to provide the initial comment.  When it
319
 
    has created the proposal, it will open it in your default web browser.
320
 
 
321
 
    The branch will be proposed to merge into SUBMIT_BRANCH.  If SUBMIT_BRANCH
322
 
    is not supplied, the remembered submit branch will be used.  If no submit
323
 
    branch is remembered, the development focus will be used.
324
 
 
325
 
    By default, the SUBMIT_BRANCH's review team will be requested to review
326
 
    the merge proposal.  This can be overriden by specifying --review (-R).
327
 
    The parameter the launchpad account name of the desired reviewer.  This
328
 
    may optionally be followed by '=' and the review type.  For example:
329
 
 
330
 
      bzr lp-propose-merge --review jrandom --review review-team=qa
331
 
 
332
 
    This will propose a merge,  request "jrandom" to perform a review of
333
 
    unspecified type, and request "review-team" to perform a "qa" review.
334
 
    """
335
 
 
336
 
    takes_options = [Option('staging',
337
 
                            help='Propose the merge on staging.'),
338
 
                     Option('message', short_name='m', type=unicode,
339
 
                            help='Commit message.'),
340
 
                     Option('approve',
341
 
                            help='Mark the proposal as approved immediately.'),
342
 
                     ListOption('review', short_name='R', type=unicode,
343
 
                            help='Requested reviewer and optional type.')]
344
 
 
345
 
    takes_args = ['submit_branch?']
346
 
 
347
 
    aliases = ['lp-submit', 'lp-propose']
348
 
 
349
 
    def run(self, submit_branch=None, review=None, staging=False,
350
 
            message=None, approve=False):
351
 
        from bzrlib.plugins.launchpad import lp_propose
352
 
        tree, branch, relpath = bzrdir.BzrDir.open_containing_tree_or_branch(
353
 
            '.')
354
 
        if review is None:
355
 
            reviews = None
356
 
        else:
357
 
            reviews = []
358
 
            for review in review:
359
 
                if '=' in review:
360
 
                    reviews.append(review.split('=', 2))
361
 
                else:
362
 
                    reviews.append((review, ''))
363
 
            if submit_branch is None:
364
 
                submit_branch = branch.get_submit_branch()
365
 
        if submit_branch is None:
366
 
            target = None
367
 
        else:
368
 
            target = _mod_branch.Branch.open(submit_branch)
369
 
        proposer = lp_propose.Proposer(tree, branch, target, message,
370
 
                                       reviews, staging, approve=approve)
371
 
        proposer.check_proposal()
372
 
        proposer.create_proposal()
373
 
 
374
 
 
375
 
register_command(cmd_lp_propose_merge)
376
 
 
377
 
 
378
 
class cmd_lp_find_proposal(Command):
379
 
 
380
 
    __doc__ = """Find the proposal to merge this revision.
381
 
 
382
 
    Finds the merge proposal(s) that discussed landing the specified revision.
383
 
    This works only if the selected branch was the merge proposal target, and
384
 
    if the merged_revno is recorded for the merge proposal.  The proposal(s)
385
 
    are opened in a web browser.
386
 
 
387
 
    Any revision involved in the merge may be specified-- the revision in
388
 
    which the merge was performed, or one of the revisions that was merged.
389
 
 
390
 
    So, to find the merge proposal that reviewed line 1 of README::
391
 
 
392
 
      bzr lp-find-proposal -r annotate:README:1
393
 
    """
394
 
 
395
 
    takes_options = ['revision']
396
 
 
397
 
    def run(self, revision=None):
398
 
        from bzrlib.plugins.launchpad import lp_api
399
 
        import webbrowser
400
 
        b = _mod_branch.Branch.open_containing('.')[0]
401
 
        pb = ui.ui_factory.nested_progress_bar()
402
 
        b.lock_read()
403
 
        try:
404
 
            revno = self._find_merged_revno(revision, b, pb)
405
 
            merged = self._find_proposals(revno, b, pb)
406
 
            if len(merged) == 0:
407
 
                raise BzrCommandError(gettext('No review found.'))
408
 
            trace.note(gettext('%d proposals(s) found.') % len(merged))
409
 
            for mp in merged:
410
 
                webbrowser.open(lp_api.canonical_url(mp))
411
 
        finally:
412
 
            b.unlock()
413
 
            pb.finished()
414
 
 
415
 
    def _find_merged_revno(self, revision, b, pb):
416
 
        if revision is None:
417
 
            return b.revno()
418
 
        pb.update(gettext('Finding revision-id'))
419
 
        revision_id = revision[0].as_revision_id(b)
420
 
        # a revno spec is necessarily on the mainline.
421
 
        if self._is_revno_spec(revision[0]):
422
 
            merging_revision = revision_id
423
 
        else:
424
 
            graph = b.repository.get_graph()
425
 
            pb.update(gettext('Finding merge'))
426
 
            merging_revision = graph.find_lefthand_merger(
427
 
                revision_id, b.last_revision())
428
 
            if merging_revision is None:
429
 
                raise InvalidRevisionSpec(revision[0].user_spec, b)
430
 
        pb.update(gettext('Finding revno'))
431
 
        return b.revision_id_to_revno(merging_revision)
432
 
 
433
 
    def _find_proposals(self, revno, b, pb):
434
 
        launchpad = lp_api.login(lp_registration.LaunchpadService())
435
 
        pb.update(gettext('Finding Launchpad branch'))
436
 
        lpb = lp_api.LaunchpadBranch.from_bzr(launchpad, b,
437
 
                                              create_missing=False)
438
 
        pb.update(gettext('Finding proposals'))
439
 
        return list(lpb.lp.getMergeProposals(status=['Merged'],
440
 
                                             merged_revnos=[revno]))
441
 
 
442
 
 
443
 
    @staticmethod
444
 
    def _is_revno_spec(spec):
445
 
        try:
446
 
            int(spec.user_spec)
447
 
        except ValueError:
448
 
            return False
449
 
        else:
450
 
            return True
451
 
 
452
 
 
453
 
register_command(cmd_lp_find_proposal)
 
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")
454
68
 
455
69
 
456
70
def _register_directory():
505
119
    info = _get_package_branch_info(the_branch.base)
506
120
    if info is None:
507
121
        return
508
 
    c = the_branch.get_config()
509
 
    verbosity = c.get_user_option('launchpad.packaging_verbosity')
510
 
    if verbosity is not None:
511
 
        verbosity = verbosity.lower()
512
 
    if verbosity == 'off':
 
122
    c = the_branch.get_config_stack()
 
123
    verbosity = c.get('launchpad.packaging_verbosity')
 
124
    if not verbosity:
513
125
        trace.mutter('not checking %s because verbosity is turned off'
514
126
                     % (the_branch.base,))
515
127
        return
575
187
topic_registry.register('launchpad',
576
188
    _launchpad_help,
577
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."))