1
# Copyright (C) 2006-2012 Canonical Ltd
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.
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.
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
17
"""Launchpad plugin commands."""
19
from __future__ import absolute_import
22
branch as _mod_branch,
26
from bzrlib.commands import (
29
from bzrlib.errors import (
35
from bzrlib.i18n import gettext
36
from bzrlib.option import (
42
class cmd_register_branch(Command):
43
__doc__ = """Register a branch with launchpad.net.
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.
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.
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
62
bzr register-branch http://foo.com/bzr/fooproject.mine \\
65
takes_args = ['public_url?']
68
'Launchpad project short name to associate with the branch.',
71
'Launchpad product short name to associate with the branch.',
75
'Short name for the branch; '
76
'by default taken from the last component of the url.',
78
Option('branch-title',
79
'One-sentence description of the branch.',
81
Option('branch-description',
82
'Longer description of the purpose or contents of the branch.',
85
"Branch author's email address, if not yourself.",
88
'The bug this branch fixes.',
91
'Prepare the request but don\'t actually send it.')
101
branch_description='',
105
from bzrlib.plugins.launchpad.lp_registration import (
106
BranchRegistrationRequest, BranchBugLinkRequest,
107
DryRunLaunchpadService, LaunchpadService)
108
if public_url is None:
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:
121
'--product is deprecated; please use --project.'))
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,
131
linko = BranchBugLinkRequest(branch_url=public_url,
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
141
# Run on service entirely in memory
142
service = DryRunLaunchpadService()
143
service.gather_user_credentials()
146
linko.submit(service)
147
self.outf.write('Branch registered.\n')
150
class cmd_launchpad_open(Command):
151
__doc__ = """Open a Launchpad branch page in your web browser."""
153
aliases = ['lp-open']
156
'Do not actually open the browser. Just say the URL we would '
159
takes_args = ['location?']
161
def _possible_locations(self, location):
162
"""Yield possible external locations for the branch at 'location'."""
165
branch = _mod_branch.Branch.open_containing(location)[0]
166
except NotBranchError:
168
branch_url = branch.get_public_branch()
169
if branch_url is not None:
171
branch_url = branch.get_push_location()
172
if branch_url is not None:
175
def _get_web_url(self, service, location):
176
from bzrlib.plugins.launchpad.lp_registration import (
178
for branch_url in self._possible_locations(location):
180
return service.get_web_url_from_branch_url(branch_url)
181
except (NotLaunchpadBranch, InvalidURL):
183
raise NotLaunchpadBranch(branch_url)
185
def run(self, location=None, dry_run=False):
186
from bzrlib.plugins.launchpad.lp_registration import (
190
web_url = self._get_web_url(LaunchpadService(), location)
191
trace.note(gettext('Opening %s in web browser') % web_url)
193
import webbrowser # this import should not be lazy
194
# otherwise bzr.exe lacks this module
195
webbrowser.open(web_url)
198
class cmd_launchpad_login(Command):
199
__doc__ = """Show or set the Launchpad user ID.
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.
206
Show the Launchpad ID of the current user::
210
Set the Launchpad ID of the current user to 'bob'::
212
bzr launchpad-login bob
214
aliases = ['lp-login']
215
takes_args = ['name?']
219
"Don't check that the user name is valid."),
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
228
username = account.get_lp_login()
231
account.check_lp_login(username)
233
self.outf.write(gettext(
234
"Launchpad user ID exists and has SSH keys.\n"))
235
self.outf.write(username + '\n')
237
self.outf.write(gettext('No Launchpad user ID configured.\n'))
242
account.check_lp_login(name)
244
self.outf.write(gettext(
245
"Launchpad user ID exists and has SSH keys.\n"))
246
account.set_lp_login(name)
248
self.outf.write(gettext("Launchpad user ID set to '%s'.\n") %
252
# XXX: cmd_launchpad_mirror is untested
253
class cmd_launchpad_mirror(Command):
254
__doc__ = """Ask Launchpad to mirror a branch now."""
256
aliases = ['lp-mirror']
257
takes_args = ['location?']
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()
270
class cmd_lp_propose_merge(Command):
271
__doc__ = """Propose merging a branch on Launchpad.
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.
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.
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:
285
bzr lp-propose-merge --review jrandom --review review-team=qa
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.
291
takes_options = [Option('staging',
292
help='Propose the merge on staging.'),
293
Option('message', short_name='m', type=unicode,
294
help='Commit message.'),
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.')]
302
takes_args = ['submit_branch?']
304
aliases = ['lp-submit', 'lp-propose']
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(
315
for review in review:
317
reviews.append(review.split('=', 2))
319
reviews.append((review, ''))
320
if submit_branch is None:
321
submit_branch = branch.get_submit_branch()
322
if submit_branch is None:
325
target = _mod_branch.Branch.open(submit_branch)
326
proposer = lp_propose.Proposer(tree, branch, target, message,
327
reviews, staging, approve=approve,
329
proposer.check_proposal()
330
proposer.create_proposal()
333
class cmd_lp_find_proposal(Command):
335
__doc__ = """Find the proposal to merge this revision.
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.
341
Only the revision specified is searched for. To find the mainline
342
revision that merged it into mainline, use the "mainline" revision spec.
344
So, to find the merge proposal that reviewed line 1 of README::
346
bzr lp-find-proposal -r mainline:annotate:README:1
349
takes_options = ['revision']
351
def run(self, revision=None):
352
from bzrlib import ui
353
from bzrlib.plugins.launchpad import lp_api
355
b = _mod_branch.Branch.open_containing('.')[0]
356
pb = ui.ui_factory.nested_progress_bar()
360
revision_id = b.last_revision()
362
revision_id = revision[0].as_revision_id(b)
363
merged = self._find_proposals(revision_id, pb)
365
raise BzrCommandError(gettext('No review found.'))
366
trace.note(gettext('%d proposals(s) found.') % len(merged))
368
webbrowser.open(lp_api.canonical_url(mp))
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(),
378
pb.update(gettext('Finding proposals'))
379
return list(launchpad.branches.getMergeProposals(
380
merged_revision=revision_id))