~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

(vila) Fix bzrlib.tests.test_gpg.TestVerify.test_verify_revoked_signature
 with recent versions of gpg. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

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