Merge lp:~jelmer/brz/land into lp:brz

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/land
Merge into: lp:brz
Diff against target: 265 lines (+129/-1)
7 files modified
breezy/plugins/propose/__init__.py (+1/-0)
breezy/plugins/propose/cmds.py (+13/-0)
breezy/plugins/propose/github.py (+8/-0)
breezy/plugins/propose/gitlabs.py (+44/-0)
breezy/plugins/propose/launchpad.py (+36/-0)
breezy/plugins/propose/propose.py (+24/-1)
doc/en/release-notes/brz-3.1.txt (+3/-0)
To merge this branch: bzr merge lp:~jelmer/brz/land
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+366609@code.launchpad.net

Commit message

Add a 'brz land' subcommand.

Description of the change

Add a 'brz land' subcommand.

This allows merging existing merge proposals on Launchpad, GitHub and GitLab.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'breezy/plugins/propose/__init__.py'
--- breezy/plugins/propose/__init__.py 2019-01-02 19:21:51 +0000
+++ breezy/plugins/propose/__init__.py 2019-06-03 23:27:22 +0000
@@ -21,6 +21,7 @@
21from ...commands import plugin_cmds21from ...commands import plugin_cmds
2222
23plugin_cmds.register_lazy("cmd_propose_merge", ["propose"], __name__ + ".cmds")23plugin_cmds.register_lazy("cmd_propose_merge", ["propose"], __name__ + ".cmds")
24plugin_cmds.register_lazy("cmd_land_merge_proposal", ["land"], __name__ + ".cmds")
24plugin_cmds.register_lazy("cmd_publish_derived", ['publish'], __name__ + ".cmds")25plugin_cmds.register_lazy("cmd_publish_derived", ['publish'], __name__ + ".cmds")
25plugin_cmds.register_lazy("cmd_find_merge_proposal", ['find-proposal'], __name__ + ".cmds")26plugin_cmds.register_lazy("cmd_find_merge_proposal", ['find-proposal'], __name__ + ".cmds")
26plugin_cmds.register_lazy("cmd_github_login", ["gh-login"], __name__ + ".cmds")27plugin_cmds.register_lazy("cmd_github_login", ["gh-login"], __name__ + ".cmds")
2728
=== modified file 'breezy/plugins/propose/cmds.py'
--- breezy/plugins/propose/cmds.py 2019-04-27 15:53:53 +0000
+++ breezy/plugins/propose/cmds.py 2019-06-03 23:27:22 +0000
@@ -340,3 +340,16 @@
340 for instance in hoster_cls.iter_instances():340 for instance in hoster_cls.iter_instances():
341 for mp in instance.iter_my_proposals(status=status):341 for mp in instance.iter_my_proposals(status=status):
342 self.outf.write('%s\n' % mp.url)342 self.outf.write('%s\n' % mp.url)
343
344
345class cmd_land_merge_proposal(Command):
346 __doc__ = """Land a merge proposal."""
347
348 takes_args = ['url']
349 takes_options = [
350 Option('message', help='Commit message to use.', type=str)]
351
352 def run(self, url, message=None):
353 from .propose import get_proposal_by_url
354 proposal = get_proposal_by_url(url)
355 proposal.merge(commit_message=message)
343356
=== modified file 'breezy/plugins/propose/github.py'
--- breezy/plugins/propose/github.py 2019-04-27 16:03:23 +0000
+++ breezy/plugins/propose/github.py 2019-06-03 23:27:22 +0000
@@ -135,6 +135,10 @@
135 def close(self):135 def close(self):
136 self._pr.edit(state='closed')136 self._pr.edit(state='closed')
137137
138 def merge(self, commit_message=None):
139 # https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button
140 self._pr.merge(commit_message=commit_message)
141
138142
139def parse_github_url(url):143def parse_github_url(url):
140 (scheme, user, password, host, port, path) = urlutils.parse_url(144 (scheme, user, password, host, port, path) = urlutils.parse_url(
@@ -321,6 +325,10 @@
321 for issue in self.gh.search_issues(query=' '.join(query)):325 for issue in self.gh.search_issues(query=' '.join(query)):
322 yield GitHubMergeProposal(issue.as_pull_request())326 yield GitHubMergeProposal(issue.as_pull_request())
323327
328 @convert_github_error
329 def get_proposal_by_url(self, url):
330 raise UnsupportedHoster(url)
331
324332
325class GitHubMergeProposalBuilder(MergeProposalBuilder):333class GitHubMergeProposalBuilder(MergeProposalBuilder):
326334
327335
=== modified file 'breezy/plugins/propose/gitlabs.py'
--- breezy/plugins/propose/gitlabs.py 2019-04-27 16:03:23 +0000
+++ breezy/plugins/propose/gitlabs.py 2019-06-03 23:27:22 +0000
@@ -54,6 +54,16 @@
54 self.url = url54 self.url = url
5555
5656
57class NotMergeRequestUrl(errors.BzrError):
58
59 _fmt = "Not a merge proposal URL: %(url)s"
60
61 def __init__(self, host, url):
62 errors.BzrError.__init__(self)
63 self.host = host
64 self.url = url
65
66
57class DifferentGitLabInstances(errors.BzrError):67class DifferentGitLabInstances(errors.BzrError):
5868
59 _fmt = ("Can't create merge proposals across GitLab instances: "69 _fmt = ("Can't create merge proposals across GitLab instances: "
@@ -129,6 +139,20 @@
129 return host, path, branch.name139 return host, path, branch.name
130140
131141
142def parse_gitlab_merge_request_url(url):
143 (scheme, user, password, host, port, path) = urlutils.parse_url(
144 url)
145 if scheme not in ('git+ssh', 'https', 'http'):
146 raise NotGitLabUrl(url)
147 if not host:
148 raise NotGitLabUrl(url)
149 path = path.strip('/')
150 parts = path.split('/')
151 if parts[-2] != 'merge_requests':
152 raise NotMergeRequestUrl(host, url)
153 return host, '/'.join(parts[:-2]), int(parts[-1])
154
155
132class GitLabMergeProposal(MergeProposal):156class GitLabMergeProposal(MergeProposal):
133157
134 def __init__(self, mr):158 def __init__(self, mr):
@@ -167,6 +191,10 @@
167 self._mr.state_event = 'close'191 self._mr.state_event = 'close'
168 self._mr.save()192 self._mr.save()
169193
194 def merge(self, commit_message=None):
195 # https://docs.gitlab.com/ee/api/merge_requests.html#accept-mr
196 self._mr.merge(merge_commit_message=commit_message)
197
170198
171def gitlab_url_to_bzr_url(url, name):199def gitlab_url_to_bzr_url(url, name):
172 if not PY3:200 if not PY3:
@@ -334,6 +362,22 @@
334 owner=self.gl.user.username, state=state):362 owner=self.gl.user.username, state=state):
335 yield GitLabMergeProposal(mp)363 yield GitLabMergeProposal(mp)
336364
365 def get_proposal_by_url(self, url):
366 try:
367 (host, project, merge_id) = parse_gitlab_merge_request_url(url)
368 except NotGitLabUrl:
369 raise UnsupportedHoster(url)
370 except NotMergeRequestUrl as e:
371 if self.gl.url == ('https://%s' % e.host):
372 raise
373 else:
374 raise UnsupportedHoster(url)
375 if self.gl.url != ('https://%s' % host):
376 raise UnsupportedHoster(url)
377 project = self.gl.projects.get(project)
378 mr = project.mergerequests.get(merge_id)
379 return GitLabMergeProposal(mr)
380
337381
338class GitlabMergeProposalBuilder(MergeProposalBuilder):382class GitlabMergeProposalBuilder(MergeProposalBuilder):
339383
340384
=== modified file 'breezy/plugins/propose/launchpad.py'
--- breezy/plugins/propose/launchpad.py 2019-06-03 22:38:47 +0000
+++ breezy/plugins/propose/launchpad.py 2019-06-03 23:27:22 +0000
@@ -20,6 +20,8 @@
20from __future__ import absolute_import20from __future__ import absolute_import
2121
22import re22import re
23import shutil
24import tempfile
2325
24from .propose import (26from .propose import (
25 Hoster,27 Hoster,
@@ -152,6 +154,23 @@
152 def close(self):154 def close(self):
153 self._mp.setStatus(status='Rejected')155 self._mp.setStatus(status='Rejected')
154156
157 def merge(self, commit_message=None):
158 target_branch = _mod_branch.Branch.open(
159 self.get_target_branch_url())
160 source_branch = _mod_branch.Branch.open(
161 self.get_source_branch_url())
162 # TODO(jelmer): Ideally this would use a memorytree, but merge doesn't
163 # support that yet.
164 # tree = target_branch.create_memorytree()
165 tmpdir = tempfile.mkdtemp()
166 try:
167 tree = target_branch.create_checkout(
168 to_location=tmpdir, lightweight=True)
169 tree.merge_from_branch(source_branch)
170 tree.commit(commit_message or self._mp.commit_message)
171 finally:
172 shutil.rmtree(tmpdir)
173
155174
156class Launchpad(Hoster):175class Launchpad(Hoster):
157 """The Launchpad hosting service."""176 """The Launchpad hosting service."""
@@ -400,6 +419,23 @@
400 for mp in self.launchpad.me.getMergeProposals(status=statuses):419 for mp in self.launchpad.me.getMergeProposals(status=statuses):
401 yield LaunchpadMergeProposal(mp)420 yield LaunchpadMergeProposal(mp)
402421
422 def get_proposal_by_url(self, url):
423 # Launchpad doesn't have a way to find a merge proposal by URL.
424 (scheme, user, password, host, port, path) = urlutils.parse_url(
425 url)
426 LAUNCHPAD_CODE_DOMAINS = [
427 ('code.%s' % domain) for domain in lp_api.LAUNCHPAD_DOMAINS.values()]
428 if host not in LAUNCHPAD_CODE_DOMAINS:
429 raise UnsupportedHoster(url)
430 # TODO(jelmer): Check if this is a launchpad URL. Otherwise, raise
431 # UnsupportedHoster
432 # See https://api.launchpad.net/devel/#branch_merge_proposal
433 # the syntax is:
434 # https://api.launchpad.net/devel/~<author.name>/<project.name>/<branch.name>/+merge/<id>
435 api_url = str(self.launchpad._root_uri) + path
436 mp = self.launchpad.load(api_url)
437 return LaunchpadMergeProposal(mp)
438
403439
404class LaunchpadBazaarMergeProposalBuilder(MergeProposalBuilder):440class LaunchpadBazaarMergeProposalBuilder(MergeProposalBuilder):
405441
406442
=== modified file 'breezy/plugins/propose/propose.py'
--- breezy/plugins/propose/propose.py 2019-04-27 16:03:23 +0000
+++ breezy/plugins/propose/propose.py 2019-06-03 23:27:22 +0000
@@ -135,6 +135,10 @@
135 """Check whether this merge proposal has been merged."""135 """Check whether this merge proposal has been merged."""
136 raise NotImplementedError(self.is_merged)136 raise NotImplementedError(self.is_merged)
137137
138 def merge(self, commit_message=None):
139 """Merge this merge proposal."""
140 raise NotImplementedError(self.merge)
141
138142
139class MergeProposalBuilder(object):143class MergeProposalBuilder(object):
140 """Merge proposal creator.144 """Merge proposal creator.
@@ -225,7 +229,7 @@
225 raise NotImplementedError(self.get_proposer)229 raise NotImplementedError(self.get_proposer)
226230
227 def iter_proposals(self, source_branch, target_branch, status='open'):231 def iter_proposals(self, source_branch, target_branch, status='open'):
228 """Get a merge proposal for a specified branch tuple.232 """Get the merge proposals for a specified branch tuple.
229233
230 :param source_branch: Source branch234 :param source_branch: Source branch
231 :param target_branch: Target branch235 :param target_branch: Target branch
@@ -234,6 +238,15 @@
234 """238 """
235 raise NotImplementedError(self.iter_proposals)239 raise NotImplementedError(self.iter_proposals)
236240
241 def get_proposal_by_url(self, url):
242 """Retrieve a branch proposal by URL.
243
244 :param url: Merge proposal URL.
245 :return: MergeProposal object
246 :raise UnsupportedHoster: Hoster does not support this URL
247 """
248 raise NotImplementedError(self.get_proposal_by_url)
249
237 def hosts(self, branch):250 def hosts(self, branch):
238 """Return true if this hoster hosts given branch."""251 """Return true if this hoster hosts given branch."""
239 raise NotImplementedError(self.hosts)252 raise NotImplementedError(self.hosts)
@@ -289,6 +302,16 @@
289 raise UnsupportedHoster(branch)302 raise UnsupportedHoster(branch)
290303
291304
305def get_proposal_by_url(url):
306 for name, hoster_cls in hosters.items():
307 for instance in hoster_cls.iter_instances():
308 try:
309 return instance.get_proposal_by_url(url)
310 except UnsupportedHoster:
311 pass
312 raise UnsupportedHoster(url)
313
314
292hosters = registry.Registry()315hosters = registry.Registry()
293hosters.register_lazy(316hosters.register_lazy(
294 "launchpad", "breezy.plugins.propose.launchpad",317 "launchpad", "breezy.plugins.propose.launchpad",
295318
=== modified file 'doc/en/release-notes/brz-3.1.txt'
--- doc/en/release-notes/brz-3.1.txt 2019-06-03 22:38:47 +0000
+++ doc/en/release-notes/brz-3.1.txt 2019-06-03 23:27:22 +0000
@@ -21,6 +21,9 @@
2121
22.. New commands, options, etc that users may wish to try out.22.. New commands, options, etc that users may wish to try out.
2323
24 * A new ``brz land`` command can merge merge proposals on Launchpad,
25 GitHub and GitLab sites. (Jelmer Vernooij, #1816213)
26
24 * The 'patch' command is now bundled with brz.27 * The 'patch' command is now bundled with brz.
25 Imported from bzrtools by Aaron Bentley. (Jelmer Vernooij)28 Imported from bzrtools by Aaron Bentley. (Jelmer Vernooij)
2629

Subscribers

People subscribed via source and target branches