~bzr-pqm/bzr/bzr.dev

6491.2.1 by Jelmer Vernooij
lazily load launchpad plugin commands.
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
6491.2.2 by Jelmer Vernooij
Use absolute imports in new file.
19
from __future__ import absolute_import
20
6491.2.1 by Jelmer Vernooij
lazily load launchpad plugin commands.
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',
6570.1.3 by Jonathan Lange
Document the behaviour
296
                            help=('Mark the proposal as approved immediately, '
297
                                  'setting the approved revision to tip.')),
6491.2.1 by Jelmer Vernooij
lazily load launchpad plugin commands.
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.
6538.2.2 by Aaron Bentley
Look up merge proposals by exact revision-id.
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.
6491.2.1 by Jelmer Vernooij
lazily load launchpad plugin commands.
340
6538.2.2 by Aaron Bentley
Look up merge proposals by exact revision-id.
341
    Only the revision specified is searched for.  To find the mainline
342
    revision that merged it into mainline, use the "mainline" revision spec.
6491.2.1 by Jelmer Vernooij
lazily load launchpad plugin commands.
343
344
    So, to find the merge proposal that reviewed line 1 of README::
345
6538.2.2 by Aaron Bentley
Look up merge proposals by exact revision-id.
346
      bzr lp-find-proposal -r mainline:annotate:README:1
6491.2.1 by Jelmer Vernooij
lazily load launchpad plugin commands.
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:
6538.2.2 by Aaron Bentley
Look up merge proposals by exact revision-id.
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)
6491.2.1 by Jelmer Vernooij
lazily load launchpad plugin commands.
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
6538.2.2 by Aaron Bentley
Look up merge proposals by exact revision-id.
373
    def _find_proposals(self, revision_id, pb):
6491.2.1 by Jelmer Vernooij
lazily load launchpad plugin commands.
374
        from bzrlib.plugins.launchpad import (lp_api, lp_registration)
6538.2.5 by Aaron Bentley
Updates from review.
375
        # "devel" because branches.getMergeProposals is not part of 1.0 API.
6538.2.3 by Aaron Bentley
Fix default version for login.
376
        launchpad = lp_api.login(lp_registration.LaunchpadService(),
377
                                 version='devel')
6491.2.1 by Jelmer Vernooij
lazily load launchpad plugin commands.
378
        pb.update(gettext('Finding proposals'))
6538.2.2 by Aaron Bentley
Look up merge proposals by exact revision-id.
379
        return list(launchpad.branches.getMergeProposals(
6538.2.3 by Aaron Bentley
Fix default version for login.
380
                    merged_revision=revision_id))