Merge lp:~jelmer/bzr/lp-plugin-lazy into lp:bzr

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Martin Packman
Approved revision: no longer in the source branch.
Merged at revision: 6496
Proposed branch: lp:~jelmer/bzr/lp-plugin-lazy
Merge into: lp:bzr
Diff against target: 851 lines (+422/-403)
2 files modified
bzrlib/plugins/launchpad/__init__.py (+12/-403)
bzrlib/plugins/launchpad/cmds.py (+410/-0)
To merge this branch: bzr merge lp:~jelmer/bzr/lp-plugin-lazy
Reviewer Review Type Date Requested Status
Martin Packman (community) Approve
Review via email: mp+96892@code.launchpad.net

Commit message

Lazily load launchpad plugin commands.

Description of the change

Lazily load launchpad plugin commands.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Looks fine, though unlikely to have a big impact.

Seems like _register_hooks should be replaced with a install_lazy_named_hook call, which would mean the _mod_branch import could go as well?

     # Since we are a built-in plugin we share the bzrlib version
 + trace,
      version_info,

Addition split the comment from the version_info line it's talking about. Mixing up module and object imports is a bit ick, but that's python.

review: Needs Information
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Am 12/03/12 00:46, schrieb Martin Packman:
> Review: Needs Information
>
> Looks fine, though unlikely to have a big impact.
>
> Seems like _register_hooks should be replaced with a install_lazy_named_hook call, which would mean the _mod_branch import could go as well?
I guess, although branch gets imported anyway by other bits of the code
so I don't think that would be all that useful.
>
> # Since we are a built-in plugin we share the bzrlib version
> + trace,
> version_info,
>
> Addition split the comment from the version_info line it's talking about. Mixing up module and object imports is a bit ick, but that's python.
Whoops, thanks.

Cheers,

jelmer

Revision history for this message
Martin Packman (gz) wrote :

Sort of underlines the number of different lazy registration things we have...

review: Approve

Preview Diff

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