~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Jelmer Vernooij
  • Date: 2012-04-02 01:44:26 UTC
  • mfrom: (6518 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6519.
  • Revision ID: jelmer@samba.org-20120402014426-0o5qtysohyl006b2
merge bzr.dev.

Show diffs side-by-side

added added

removed removed

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