14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Launchpad.net integration plugin for Bazaar."""
17
"""Launchpad.net integration plugin for Bazaar.
19
This plugin provides facilities for working with Bazaar branches that are
20
hosted on Launchpad (http://launchpad.net). It provides a directory service
21
for referring to Launchpad branches using the "lp:" prefix. For example,
22
lp:bzr refers to the Bazaar's main development branch and
23
lp:~username/project/branch-name can be used to refer to a specific branch.
25
This plugin provides a bug tracker so that "bzr commit --fixes lp:1234" will
26
record that revision as fixing Launchpad's bug 1234.
28
The plugin also provides the following commands:
30
launchpad-login: Show or set the Launchpad user ID
31
launchpad-open: Open a Launchpad branch page in your web browser
32
lp-propose-merge: Propose merging a branch on Launchpad
33
register-branch: Register a branch with launchpad.net
34
launchpad-mirror: Ask Launchpad to mirror a branch now
38
from __future__ import absolute_import
19
40
# The XMLRPC server address can be overridden by setting the environment
20
41
# variable $BZR_LP_XMLRPC_URL
22
# see http://bazaar-vcs.org/Specs/BranchRegistrationTool
24
# Since we are a built-in plugin we share the bzrlib version
25
from bzrlib import version_info
27
from bzrlib.lazy_import import lazy_import
28
lazy_import(globals(), """
43
# see http://wiki.bazaar.canonical.com/Specs/BranchRegistrationTool
29
45
from bzrlib import (
30
46
branch as _mod_branch,
47
config as _mod_config,
49
# Since we are a built-in plugin we share the bzrlib version
35
from bzrlib import bzrdir
36
53
from bzrlib.commands import (
40
56
from bzrlib.directory_service import directories
41
from bzrlib.errors import (
48
57
from bzrlib.help_topics import topic_registry
49
from bzrlib.option import (
55
class cmd_register_branch(Command):
56
__doc__ = """Register a branch with launchpad.net.
58
This command lists a bzr branch in the directory of branches on
59
launchpad.net. Registration allows the branch to be associated with
60
bugs or specifications.
62
Before using this command you must register the project to which the
63
branch belongs, and create an account for yourself on launchpad.net.
66
public_url: The publicly visible url for the branch to register.
67
This must be an http or https url (which Launchpad can read
68
from to access the branch). Local file urls, SFTP urls, and
69
bzr+ssh urls will not work.
70
If no public_url is provided, bzr will use the configured
71
public_url if there is one for the current branch, and
75
bzr register-branch http://foo.com/bzr/fooproject.mine \\
78
takes_args = ['public_url?']
81
'Launchpad project short name to associate with the branch.',
84
'Launchpad product short name to associate with the branch.',
88
'Short name for the branch; '
89
'by default taken from the last component of the url.',
91
Option('branch-title',
92
'One-sentence description of the branch.',
94
Option('branch-description',
95
'Longer description of the purpose or contents of the branch.',
98
"Branch author's email address, if not yourself.",
101
'The bug this branch fixes.',
104
'Prepare the request but don\'t actually send it.')
114
branch_description='',
118
from bzrlib.plugins.launchpad.lp_registration import (
119
BranchRegistrationRequest, BranchBugLinkRequest,
120
DryRunLaunchpadService, LaunchpadService)
121
if public_url is None:
123
b = _mod_branch.Branch.open_containing('.')[0]
124
except NotBranchError:
125
raise BzrCommandError('register-branch requires a public '
126
'branch url - see bzr help register-branch.')
127
public_url = b.get_public_branch()
128
if public_url is None:
129
raise NoPublicBranch(b)
130
if product is not None:
132
trace.note('--product is deprecated; please use --project.')
135
rego = BranchRegistrationRequest(branch_url=public_url,
136
branch_name=branch_name,
137
branch_title=branch_title,
138
branch_description=branch_description,
139
product_name=project,
142
linko = BranchBugLinkRequest(branch_url=public_url,
145
service = LaunchpadService()
146
# This gives back the xmlrpc url that can be used for future
147
# operations on the branch. It's not so useful to print to the
148
# user since they can't do anything with it from a web browser; it
149
# might be nice for the server to tell us about an html url as
152
# Run on service entirely in memory
153
service = DryRunLaunchpadService()
154
service.gather_user_credentials()
157
linko.submit(service)
158
print 'Branch registered.'
160
register_command(cmd_register_branch)
163
class cmd_launchpad_open(Command):
164
__doc__ = """Open a Launchpad branch page in your web browser."""
166
aliases = ['lp-open']
169
'Do not actually open the browser. Just say the URL we would '
172
takes_args = ['location?']
174
def _possible_locations(self, location):
175
"""Yield possible external locations for the branch at 'location'."""
178
branch = _mod_branch.Branch.open_containing(location)[0]
179
except NotBranchError:
181
branch_url = branch.get_public_branch()
182
if branch_url is not None:
184
branch_url = branch.get_push_location()
185
if branch_url is not None:
188
def _get_web_url(self, service, location):
189
from bzrlib.plugins.launchpad.lp_registration import (
191
for branch_url in self._possible_locations(location):
193
return service.get_web_url_from_branch_url(branch_url)
194
except (NotLaunchpadBranch, InvalidURL):
196
raise NotLaunchpadBranch(branch_url)
198
def run(self, location=None, dry_run=False):
199
from bzrlib.plugins.launchpad.lp_registration import (
203
web_url = self._get_web_url(LaunchpadService(), location)
204
trace.note('Opening %s in web browser' % web_url)
206
import webbrowser # this import should not be lazy
207
# otherwise bzr.exe lacks this module
208
webbrowser.open(web_url)
210
register_command(cmd_launchpad_open)
213
class cmd_launchpad_login(Command):
214
__doc__ = """Show or set the Launchpad user ID.
216
When communicating with Launchpad, some commands need to know your
217
Launchpad user ID. This command can be used to set or show the
218
user ID that Bazaar will use for such communication.
221
Show the Launchpad ID of the current user::
225
Set the Launchpad ID of the current user to 'bob'::
227
bzr launchpad-login bob
229
aliases = ['lp-login']
230
takes_args = ['name?']
234
"Don't check that the user name is valid."),
237
def run(self, name=None, no_check=False, verbose=False):
238
# This is totally separate from any launchpadlib login system.
239
from bzrlib.plugins.launchpad import account
240
check_account = not no_check
243
username = account.get_lp_login()
246
account.check_lp_login(username)
249
"Launchpad user ID exists and has SSH keys.\n")
250
self.outf.write(username + '\n')
252
self.outf.write('No Launchpad user ID configured.\n')
257
account.check_lp_login(name)
260
"Launchpad user ID exists and has SSH keys.\n")
261
account.set_lp_login(name)
263
self.outf.write("Launchpad user ID set to '%s'.\n" % (name,))
265
register_command(cmd_launchpad_login)
268
# XXX: cmd_launchpad_mirror is untested
269
class cmd_launchpad_mirror(Command):
270
__doc__ = """Ask Launchpad to mirror a branch now."""
272
aliases = ['lp-mirror']
273
takes_args = ['location?']
275
def run(self, location='.'):
276
from bzrlib.plugins.launchpad import lp_api
277
from bzrlib.plugins.launchpad.lp_registration import LaunchpadService
278
branch = _mod_branch.Branch.open(location)
279
service = LaunchpadService()
280
launchpad = lp_api.login(service)
281
lp_branch = lp_api.load_branch(launchpad, branch)
282
lp_branch.requestMirror()
285
register_command(cmd_launchpad_mirror)
288
class cmd_lp_propose_merge(Command):
289
__doc__ = """Propose merging a branch on Launchpad.
291
This will open your usual editor to provide the initial comment. When it
292
has created the proposal, it will open it in your default web browser.
294
The branch will be proposed to merge into SUBMIT_BRANCH. If SUBMIT_BRANCH
295
is not supplied, the remembered submit branch will be used. If no submit
296
branch is remembered, the development focus will be used.
298
By default, the SUBMIT_BRANCH's review team will be requested to review
299
the merge proposal. This can be overriden by specifying --review (-R).
300
The parameter the launchpad account name of the desired reviewer. This
301
may optionally be followed by '=' and the review type. For example:
303
bzr lp-propose-merge --review jrandom --review review-team=qa
305
This will propose a merge, request "jrandom" to perform a review of
306
unspecified type, and request "review-team" to perform a "qa" review.
309
takes_options = [Option('staging',
310
help='Propose the merge on staging.'),
311
Option('message', short_name='m', type=unicode,
312
help='Commit message.'),
314
help='Mark the proposal as approved immediately.'),
315
ListOption('review', short_name='R', type=unicode,
316
help='Requested reviewer and optional type.')]
318
takes_args = ['submit_branch?']
320
aliases = ['lp-submit', 'lp-propose']
322
def run(self, submit_branch=None, review=None, staging=False,
323
message=None, approve=False):
324
from bzrlib.plugins.launchpad import lp_propose
325
tree, branch, relpath = bzrdir.BzrDir.open_containing_tree_or_branch(
331
for review in review:
333
reviews.append(review.split('=', 2))
335
reviews.append((review, ''))
336
if submit_branch is None:
337
submit_branch = branch.get_submit_branch()
338
if submit_branch is None:
341
target = _mod_branch.Branch.open(submit_branch)
342
proposer = lp_propose.Proposer(tree, branch, target, message,
343
reviews, staging, approve=approve)
344
proposer.check_proposal()
345
proposer.create_proposal()
348
register_command(cmd_lp_propose_merge)
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")
351
70
def _register_directory():
352
71
directories.register_lazy('lp:', 'bzrlib.plugins.launchpad.lp_directory',
353
72
'LaunchpadDirectory',
354
73
'Launchpad-based directory service',)
74
directories.register_lazy(
75
'debianlp:', 'bzrlib.plugins.launchpad.lp_directory',
78
directories.register_lazy(
79
'ubuntu:', 'bzrlib.plugins.launchpad.lp_directory',
355
83
_register_directory()
85
# This is kept in __init__ so that we don't load lp_api_lite unless the branch
86
# actually matches. That way we can avoid importing extra dependencies like
88
_package_branch = lazy_regex.lazy_compile(
89
r'bazaar.launchpad.net.*?/'
90
r'(?P<user>~[^/]+/)?(?P<archive>ubuntu|debian)/(?P<series>[^/]+/)?'
91
r'(?P<project>[^/]+)(?P<branch>/[^/]+)?'
94
def _get_package_branch_info(url):
95
"""Determine the packaging information for this URL.
97
:return: If this isn't a packaging branch, return None. If it is, return
98
(archive, series, project)
102
m = _package_branch.search(url)
105
archive, series, project, user = m.group('archive', 'series',
107
if series is not None:
108
# series is optional, so the regex includes the extra '/', we don't
109
# want to send that on (it causes Internal Server Errors.)
110
series = series.strip('/')
112
user = user.strip('~/')
113
if user != 'ubuntu-branches':
115
return archive, series, project
118
def _check_is_up_to_date(the_branch):
119
info = _get_package_branch_info(the_branch.base)
122
c = the_branch.get_config_stack()
123
verbosity = c.get('launchpad.packaging_verbosity')
125
trace.mutter('not checking %s because verbosity is turned off'
126
% (the_branch.base,))
128
archive, series, project = info
129
from bzrlib.plugins.launchpad import lp_api_lite
130
latest_pub = lp_api_lite.LatestPublication(archive, series, project)
131
lp_api_lite.report_freshness(the_branch, verbosity, latest_pub)
134
def _register_hooks():
135
_mod_branch.Branch.hooks.install_named_hook('open',
136
_check_is_up_to_date, 'package-branch-up-to-date')
358
141
def load_tests(basic_tests, module, loader):
359
142
testmod_names = [
363
147
'test_lp_directory',