Merge lp:~jelmer/brz/my-proposals 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/my-proposals
Merge into: lp:brz
Diff against target: 795 lines (+291/-104)
6 files modified
breezy/plugins/propose/__init__.py (+3/-0)
breezy/plugins/propose/cmds.py (+29/-2)
breezy/plugins/propose/github.py (+44/-6)
breezy/plugins/propose/gitlabs.py (+78/-25)
breezy/plugins/propose/launchpad.py (+108/-58)
breezy/plugins/propose/propose.py (+29/-13)
To merge this branch: bzr merge lp:~jelmer/brz/my-proposals
Reviewer Review Type Date Requested Status
Martin Packman Approve
Review via email: mp+361363@code.launchpad.net

Commit message

Add 'bzr my-proposals' command.

Description of the change

Add 'bzr my-proposals' command.

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

Thanks! Changes look good, one teeny nit inline.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/plugins/propose/__init__.py'
2--- breezy/plugins/propose/__init__.py 2018-12-11 14:12:47 +0000
3+++ breezy/plugins/propose/__init__.py 2019-01-08 21:33:41 +0000
4@@ -25,3 +25,6 @@
5 plugin_cmds.register_lazy("cmd_find_merge_proposal", ['find-proposal'], __name__ + ".cmds")
6 plugin_cmds.register_lazy("cmd_github_login", ["gh-login"], __name__ + ".cmds")
7 plugin_cmds.register_lazy("cmd_gitlab_login", ["gl-login"], __name__ + ".cmds")
8+plugin_cmds.register_lazy(
9+ "cmd_my_merge_proposals", ["my-proposals"],
10+ __name__ + ".cmds")
11
12=== modified file 'breezy/plugins/propose/cmds.py'
13--- breezy/plugins/propose/cmds.py 2019-01-01 22:31:13 +0000
14+++ breezy/plugins/propose/cmds.py 2019-01-08 21:33:41 +0000
15@@ -223,8 +223,8 @@
16 else:
17 target = _mod_branch.Branch.open(submit_branch)
18 hoster = _mod_propose.get_hoster(branch)
19- mp = hoster.get_proposal(branch, target)
20- self.outf.write(gettext('Merge proposal: %s\n') % mp.url)
21+ for mp in hoster.iter_proposals(branch, target):
22+ self.outf.write(gettext('Merge proposal: %s\n') % mp.url)
23
24
25 class cmd_github_login(Command):
26@@ -300,3 +300,30 @@
27 gl = Gitlab(url=url, private_token=private_token)
28 gl.auth()
29 store_gitlab_token(name=name, url=url, private_token=private_token)
30+
31+
32+class cmd_my_merge_proposals(Command):
33+ __doc__ = """List all merge proposals owned by the logged-in user.
34+
35+ """
36+
37+ hidden = True
38+
39+ takes_options = [
40+ RegistryOption.from_kwargs(
41+ 'status',
42+ title='Proposal Status',
43+ help='Only include proposals with specified status.',
44+ value_switches=True,
45+ enum_switch=True,
46+ all='All merge proposals',
47+ open='Open merge proposals',
48+ merged='Merged merge proposals',
49+ closed='Closed merge proposals')]
50+
51+ def run(self, status='open'):
52+ from .propose import hosters
53+ for name, hoster_cls in hosters.items():
54+ for instance in hoster_cls.iter_instances():
55+ for mp in instance.iter_my_proposals(status=status):
56+ self.outf.write('%s\n' % mp.url)
57
58=== modified file 'breezy/plugins/propose/github.py'
59--- breezy/plugins/propose/github.py 2019-01-01 22:31:13 +0000
60+++ breezy/plugins/propose/github.py 2019-01-08 21:33:41 +0000
61@@ -25,7 +25,6 @@
62 MergeProposal,
63 MergeProposalBuilder,
64 MergeProposalExists,
65- NoMergeProposal,
66 PrerequisiteBranchUnsupported,
67 UnsupportedHoster,
68 )
69@@ -105,6 +104,15 @@
70 def url(self):
71 return self._pr.html_url
72
73+ def _branch_from_part(self, part):
74+ return github_url_to_bzr_url(part.repo.html_url, part.ref)
75+
76+ def get_source_branch_url(self):
77+ return self._branch_from_part(self._pr.head)
78+
79+ def get_target_branch_url(self):
80+ return self._branch_from_part(self._pr.base)
81+
82 def get_description(self):
83 return self._pr.body
84
85@@ -136,6 +144,8 @@
86
87 class GitHub(Hoster):
88
89+ name = 'github'
90+
91 supports_merge_proposal_labels = True
92
93 def __repr__(self):
94@@ -205,20 +215,30 @@
95 def get_proposer(self, source_branch, target_branch):
96 return GitHubMergeProposalBuilder(self.gh, source_branch, target_branch)
97
98- def get_proposal(self, source_branch, target_branch):
99+ def iter_proposals(self, source_branch, target_branch, status='open'):
100 (source_owner, source_repo_name, source_branch_name) = (
101 parse_github_url(source_branch))
102 (target_owner, target_repo_name, target_branch_name) = (
103 parse_github_url(target_branch))
104- target_repo = self.gh.get_repo("%s/%s" % (target_owner, target_repo_name))
105- for pull in target_repo.get_pulls(head=target_branch_name):
106+ target_repo = self.gh.get_repo(
107+ "%s/%s" % (target_owner, target_repo_name))
108+ state = {
109+ 'open': 'open',
110+ 'merged': 'closed',
111+ 'closed': 'closed',
112+ 'all': 'all'}
113+ for pull in target_repo.get_pulls(
114+ head=target_branch_name,
115+ state=state[status]):
116+ if (status == 'closed' and pull.merged or
117+ status == 'merged' and not pull.merged):
118+ continue
119 if pull.head.ref != source_branch_name:
120 continue
121 if (pull.head.repo.owner.login != source_owner or
122 pull.head.repo.name != source_repo_name):
123 continue
124- return GitHubMergeProposal(pull)
125- raise NoMergeProposal()
126+ yield GitHubMergeProposal(pull)
127
128 def hosts(self, branch):
129 try:
130@@ -236,6 +256,24 @@
131 raise UnsupportedHoster(branch)
132 return cls()
133
134+ @classmethod
135+ def iter_instances(cls):
136+ yield cls()
137+
138+ def iter_my_proposals(self, status='open'):
139+ query = ['is:pr']
140+ if status == 'open':
141+ query.append('is:open')
142+ elif status == 'closed':
143+ # Note that we don't use is:closed here, since that also includes
144+ # merged pull requests.
145+ query.append('is:unmerged')
146+ elif status == 'merged':
147+ query.append('is:merged')
148+ query.append('author:%s' % self.gh.get_user().login)
149+ for issue in self.gh.search_issues(query=' '.join(query)):
150+ yield GitHubMergeProposal(issue.as_pull_request())
151+
152
153 class GitHubMergeProposalBuilder(MergeProposalBuilder):
154
155
156=== modified file 'breezy/plugins/propose/gitlabs.py'
157--- breezy/plugins/propose/gitlabs.py 2019-01-01 22:31:13 +0000
158+++ breezy/plugins/propose/gitlabs.py 2019-01-08 21:33:41 +0000
159@@ -33,12 +33,18 @@
160 MergeProposal,
161 MergeProposalBuilder,
162 MergeProposalExists,
163- NoMergeProposal,
164 NoSuchProject,
165 PrerequisiteBranchUnsupported,
166 UnsupportedHoster,
167 )
168
169+def mp_status_to_status(status):
170+ return {
171+ 'all': 'all',
172+ 'open': 'opened',
173+ 'merged': 'merged',
174+ 'closed': 'closed'}[status]
175+
176
177 class NotGitLabUrl(errors.BzrError):
178
179@@ -83,26 +89,30 @@
180 config.write(f)
181
182
183+def iter_tokens():
184+ import configparser
185+ from gitlab.config import _DEFAULT_FILES
186+ config = configparser.ConfigParser()
187+ config.read(_DEFAULT_FILES + [default_config_path()])
188+ for name, section in config.items():
189+ yield name, section
190+
191+
192 def connect_gitlab(host):
193- from gitlab import Gitlab
194+ from gitlab import Gitlab, GitlabGetError
195 auth = AuthenticationConfig()
196
197 url = 'https://%s' % host
198 credentials = auth.get_credentials('https', host)
199 if credentials is None:
200- import gitlab
201- import configparser
202- from gitlab.config import _DEFAULT_FILES
203- config = configparser.ConfigParser()
204- config.read(_DEFAULT_FILES + [default_config_path()])
205- for name, section in config.items():
206+ for name, section in iter_tokens():
207 if section.get('url') == url:
208 credentials = section
209 break
210 else:
211 try:
212 return Gitlab(url)
213- except gitlab.GitlabGetError:
214+ except GitlabGetError:
215 raise GitLabLoginMissing()
216 else:
217 credentials['url'] = url
218@@ -138,8 +148,20 @@
219 def set_description(self, description):
220 self._mr.description = description
221
222+ def _branch_url_from_project(self, project_id, branch_name):
223+ project = self._mr.manager.gitlab.projects.get(project_id)
224+ return gitlab_url_to_bzr_url(project.http_url_to_repo, branch_name)
225+
226+ def get_source_branch_url(self):
227+ return self._branch_url_from_project(
228+ self._mr.source_project_id, self._mr.source_branch)
229+
230+ def get_target_branch_url(self):
231+ return self._branch_url_from_project(
232+ self._mr.target_project_id, self._mr.target_branch)
233+
234 def is_merged(self):
235- return (self._mr.attributes['state'] == 'merged')
236+ return (self._mr.state == 'merged')
237
238
239 def gitlab_url_to_bzr_url(url, name):
240@@ -164,7 +186,7 @@
241 (host, project_name, branch_name) = parse_gitlab_url(branch)
242 project = self.gl.projects.get(project_name)
243 return gitlab_url_to_bzr_url(
244- project.attributes['ssh_url_to_repo'], branch_name)
245+ project.ssh_url_to_repo, branch_name)
246
247 def publish_derived(self, local_branch, base_branch, name, project=None,
248 owner=None, revision_id=None, overwrite=False,
249@@ -190,7 +212,7 @@
250 target_project = base_project.forks.create({})
251 else:
252 raise
253- remote_repo_url = git_url_to_bzr_url(target_project.attributes['ssh_url_to_repo'])
254+ remote_repo_url = git_url_to_bzr_url(target_project.ssh_url_to_repo)
255 remote_dir = controldir.ControlDir.open(remote_repo_url)
256 try:
257 push_result = remote_dir.push_branch(
258@@ -203,7 +225,7 @@
259 local_branch, revision_id=revision_id, overwrite=overwrite,
260 name=name, lossy=True)
261 public_url = gitlab_url_to_bzr_url(
262- target_project.attributes['http_url_to_repo'], name)
263+ target_project.http_url_to_repo, name)
264 return push_result.target_branch, public_url
265
266 def get_derived_branch(self, base_branch, name, project=None, owner=None):
267@@ -228,12 +250,13 @@
268 raise errors.NotBranchError('%s/%s/%s' % (self.gl.url, owner, project))
269 raise
270 return _mod_branch.Branch.open(gitlab_url_to_bzr_url(
271- target_project.attributes['ssh_url_to_repo'], name))
272+ target_project.ssh_url_to_repo, name))
273
274 def get_proposer(self, source_branch, target_branch):
275 return GitlabMergeProposalBuilder(self.gl, source_branch, target_branch)
276
277- def get_proposal(self, source_branch, target_branch):
278+ def iter_proposals(self, source_branch, target_branch, status):
279+ import gitlab
280 (source_host, source_project_name, source_branch_name) = (
281 parse_gitlab_url(source_branch))
282 (target_host, target_project_name, target_branch_name) = (
283@@ -243,19 +266,18 @@
284 self.gl.auth()
285 source_project = self.gl.projects.get(source_project_name)
286 target_project = self.gl.projects.get(target_project_name)
287+ state = mp_status_to_status(status)
288 try:
289- for mr in target_project.mergerequests.list(state='all'):
290- attrs = mr.attributes
291- if (attrs['source_project_id'] != source_project.id or
292- attrs['source_branch'] != source_branch_name or
293- attrs['target_project_id'] != target_project.id or
294- attrs['target_branch'] != target_branch_name):
295+ for mr in target_project.mergerequests.list(state=state):
296+ if (mr.source_project_id != source_project.id or
297+ mr.source_branch != source_branch_name or
298+ mr.target_project_id != target_project.id or
299+ mr.target_branch != target_branch_name):
300 continue
301- return GitLabMergeProposal(mr)
302+ yield GitLabMergeProposal(mr)
303 except gitlab.GitlabListError as e:
304 if e.response_code == 403:
305- raise PermissionDenied(e.error_message)
306- raise NoMergeProposal()
307+ raise errors.PermissionDenied(e.error_message)
308
309 def hosts(self, branch):
310 try:
311@@ -287,6 +309,22 @@
312 raise
313 return cls(gl)
314
315+ @classmethod
316+ def iter_instances(cls):
317+ from gitlab import Gitlab
318+ for name, credentials in iter_tokens():
319+ if 'url' not in credentials:
320+ continue
321+ gl = Gitlab(**credentials)
322+ yield cls(gl)
323+
324+ def iter_my_proposals(self, status='open'):
325+ state = mp_status_to_status(status)
326+ self.gl.auth()
327+ for mp in self.gl.mergerequests.list(
328+ owner=self.gl.user.username, state=state):
329+ yield GitLabMergeProposal(mp)
330+
331
332 class GitlabMergeProposalBuilder(MergeProposalBuilder):
333
334@@ -343,8 +381,23 @@
335 merge_request = source_project.mergerequests.create(kwargs)
336 except gitlab.GitlabCreateError as e:
337 if e.response_code == 403:
338- raise PermissionDenied(e.error_message)
339+ raise errors.PermissionDenied(e.error_message)
340 if e.response_code == 409:
341 raise MergeProposalExists(self.source_branch.user_url)
342 raise
343 return GitLabMergeProposal(merge_request)
344+
345+
346+def register_gitlab_instance(shortname, url):
347+ """Register a gitlab instance.
348+
349+ :param shortname: Short name (e.g. "gitlab")
350+ :param url: URL to the gitlab instance
351+ """
352+ from breezy.bugtracker import (
353+ tracker_registry,
354+ ProjectIntegerBugTracker,
355+ )
356+ tracker_registry.register(
357+ shortname, ProjectIntegerBugTracker(
358+ shortname, url + '/{project}/issues/{id}'))
359
360=== modified file 'breezy/plugins/propose/launchpad.py'
361--- breezy/plugins/propose/launchpad.py 2019-01-01 22:31:13 +0000
362+++ breezy/plugins/propose/launchpad.py 2019-01-08 21:33:41 +0000
363@@ -27,7 +27,6 @@
364 MergeProposal,
365 MergeProposalBuilder,
366 MergeProposalExists,
367- NoMergeProposal,
368 UnsupportedHoster,
369 )
370
371@@ -38,6 +37,7 @@
372 hooks,
373 urlutils,
374 )
375+from ...git.refs import ref_to_branch_name
376 from ...lazy_import import lazy_import
377 lazy_import(globals(), """
378 from breezy.plugins.launchpad import (
379@@ -50,16 +50,20 @@
380
381 # TODO(jelmer): Make selection of launchpad staging a configuration option.
382
383-MERGE_PROPOSAL_STATUSES = [
384- 'Work in progress',
385- 'Needs review',
386- 'Approved',
387- 'Rejected',
388- 'Merged',
389- 'Code failed to merge',
390- 'Queued',
391- 'Superseded',
392- ]
393+def status_to_lp_mp_statuses(status):
394+ statuses = []
395+ if status in ('open', 'all'):
396+ statuses.extend([
397+ 'Work in progress',
398+ 'Needs review',
399+ 'Approved',
400+ 'Code failed to merge',
401+ 'Queued'])
402+ if status in ('closed', 'all'):
403+ statuses.extend(['Rejected', 'Superseded'])
404+ if status in ('merged', 'all'):
405+ statuses.append('Merged')
406+ return statuses
407
408
409 def plausible_launchpad_url(url):
410@@ -103,6 +107,26 @@
411 def __init__(self, mp):
412 self._mp = mp
413
414+ def get_source_branch_url(self):
415+ if self._mp.source_branch:
416+ return self._mp.source_branch.bzr_identity
417+ else:
418+ branch_name = ref_to_branch_name(
419+ self._mp.source_git_path.encode('utf-8'))
420+ return urlutils.join_segment_parameters(
421+ self._mp.source_git_repository.git_identity,
422+ {"branch": branch_name})
423+
424+ def get_target_branch_url(self):
425+ if self._mp.target_branch:
426+ return self._mp.target_branch.bzr_identity
427+ else:
428+ branch_name = ref_to_branch_name(
429+ self._mp.target_git_path.encode('utf-8'))
430+ return urlutils.join_segment_parameters(
431+ self._mp.target_git_repository.git_identity,
432+ {"branch": branch_name})
433+
434 @property
435 def url(self):
436 return lp_api.canonical_url(self._mp)
437@@ -114,6 +138,8 @@
438 class Launchpad(Hoster):
439 """The Launchpad hosting service."""
440
441+ name = 'launchpad'
442+
443 # https://bugs.launchpad.net/launchpad/+bug/397676
444 supports_merge_proposal_labels = False
445
446@@ -142,7 +168,8 @@
447 url, params = urlutils.split_segment_parameters(branch.user_url)
448 (scheme, user, password, host, port, path) = urlutils.parse_url(
449 url)
450- repo_lp = self.launchpad.git_repositories.getByPath(path=path.strip('/'))
451+ repo_lp = self.launchpad.git_repositories.getByPath(
452+ path=path.strip('/'))
453 try:
454 ref_path = params['ref']
455 except KeyError:
456@@ -155,13 +182,15 @@
457 return (repo_lp, ref_lp)
458
459 def _get_lp_bzr_branch_from_branch(self, branch):
460- return self.launchpad.branches.getByUrl(url=urlutils.unescape(branch.user_url))
461+ return self.launchpad.branches.getByUrl(
462+ url=urlutils.unescape(branch.user_url))
463
464 def _get_derived_git_path(self, base_path, owner, project):
465 base_repo = self.launchpad.git_repositories.getByPath(path=base_path)
466 if project is None:
467 project = '/'.join(base_repo.unique_name.split('/')[1:])
468- # TODO(jelmer): Surely there is a better way of creating one of these URLs?
469+ # TODO(jelmer): Surely there is a better way of creating one of these
470+ # URLs?
471 return "~%s/%s" % (owner, project)
472
473 def _publish_git(self, local_branch, base_path, name, owner, project=None,
474@@ -177,27 +206,31 @@
475 if dir_to is None:
476 try:
477 br_to = local_branch.create_clone_on_transport(
478- to_transport, revision_id=revision_id, name=name,
479- stacked_on=main_branch.user_url)
480+ to_transport, revision_id=revision_id, name=name)
481 except errors.NoRoundtrippingSupport:
482 br_to = local_branch.create_clone_on_transport(
483 to_transport, revision_id=revision_id, name=name,
484- stacked_on=main_branch.user_url, lossy=True)
485+ lossy=True)
486 else:
487 try:
488- dir_to = dir_to.push_branch(local_branch, revision_id, overwrite=overwrite, name=name)
489+ dir_to = dir_to.push_branch(
490+ local_branch, revision_id, overwrite=overwrite, name=name)
491 except errors.NoRoundtrippingSupport:
492 if not allow_lossy:
493 raise
494- dir_to = dir_to.push_branch(local_branch, revision_id, overwrite=overwrite, name=name, lossy=True)
495+ dir_to = dir_to.push_branch(
496+ local_branch, revision_id, overwrite=overwrite, name=name,
497+ lossy=True)
498 br_to = dir_to.target_branch
499- return br_to, ("https://git.launchpad.net/%s/+ref/%s" % (to_path, name))
500+ return br_to, (
501+ "https://git.launchpad.net/%s/+ref/%s" % (to_path, name))
502
503 def _get_derived_bzr_path(self, base_branch, name, owner, project):
504 if project is None:
505 base_branch_lp = self._get_lp_bzr_branch_from_branch(base_branch)
506 project = '/'.join(base_branch_lp.unique_name.split('/')[1:-1])
507- # TODO(jelmer): Surely there is a better way of creating one of these URLs?
508+ # TODO(jelmer): Surely there is a better way of creating one of these
509+ # URLs?
510 return "~%s/%s/%s" % (owner, project, name)
511
512 def get_push_url(self, branch):
513@@ -211,8 +244,9 @@
514 else:
515 raise AssertionError
516
517- def _publish_bzr(self, local_branch, base_branch, name, owner, project=None,
518- revision_id=None, overwrite=False, allow_lossy=True):
519+ def _publish_bzr(self, local_branch, base_branch, name, owner,
520+ project=None, revision_id=None, overwrite=False,
521+ allow_lossy=True):
522 to_path = self._get_derived_bzr_path(base_branch, name, owner, project)
523 to_transport = get_transport("lp:" + to_path)
524 try:
525@@ -222,9 +256,11 @@
526 dir_to = None
527
528 if dir_to is None:
529- br_to = local_branch.create_clone_on_transport(to_transport, revision_id=revision_id)
530+ br_to = local_branch.create_clone_on_transport(
531+ to_transport, revision_id=revision_id)
532 else:
533- br_to = dir_to.push_branch(local_branch, revision_id, overwrite=overwrite).target_branch
534+ br_to = dir_to.push_branch(
535+ local_branch, revision_id, overwrite=overwrite).target_branch
536 return br_to, ("https://code.launchpad.net/" + to_path)
537
538 def _split_url(self, url):
539@@ -239,8 +275,9 @@
540 raise ValueError("unknown host %s" % host)
541 return (vcs, user, password, path, params)
542
543- def publish_derived(self, local_branch, base_branch, name, project=None, owner=None,
544- revision_id=None, overwrite=False, allow_lossy=True):
545+ def publish_derived(self, local_branch, base_branch, name, project=None,
546+ owner=None, revision_id=None, overwrite=False,
547+ allow_lossy=True):
548 """Publish a branch to the site, derived from base_branch.
549
550 :param base_branch: branch to derive the new branch from
551@@ -252,8 +289,8 @@
552 """
553 if owner is None:
554 owner = self.launchpad.me.name
555- (base_vcs, base_user, base_password, base_path, base_params) = self._split_url(
556- base_branch.user_url)
557+ (base_vcs, base_user, base_password, base_path,
558+ base_params) = self._split_url(base_branch.user_url)
559 # TODO(jelmer): Prevent publishing to development focus
560 if base_vcs == 'bzr':
561 return self._publish_bzr(
562@@ -271,10 +308,11 @@
563 def get_derived_branch(self, base_branch, name, project=None, owner=None):
564 if owner is None:
565 owner = self.launchpad.me.name
566- (base_vcs, base_user, base_password, base_path, base_params) = self._split_url(
567- base_branch.user_url)
568+ (base_vcs, base_user, base_password, base_path,
569+ base_params) = self._split_url(base_branch.user_url)
570 if base_vcs == 'bzr':
571- to_path = self._get_derived_bzr_path(base_branch, name, owner, project)
572+ to_path = self._get_derived_bzr_path(
573+ base_branch, name, owner, project)
574 return _mod_branch.Branch.open("lp:" + to_path)
575 elif base_vcs == 'git':
576 to_path = self._get_derived_git_path(
577@@ -284,42 +322,37 @@
578 else:
579 raise AssertionError('not a valid Launchpad URL')
580
581- def get_proposal(self, source_branch, target_branch):
582- (base_vcs, base_user, base_password, base_path, base_params) = (
583- self._split_url(target_branch.user_url))
584+ def iter_proposals(self, source_branch, target_branch, status='open'):
585+ (base_vcs, base_user, base_password, base_path,
586+ base_params) = self._split_url(target_branch.user_url)
587+ statuses = status_to_lp_mp_statuses(status)
588 if base_vcs == 'bzr':
589 target_branch_lp = self.launchpad.branches.getByUrl(
590 url=target_branch.user_url)
591 source_branch_lp = self.launchpad.branches.getByUrl(
592 url=source_branch.user_url)
593- for mp in target_branch_lp.getMergeProposals(
594- status=MERGE_PROPOSAL_STATUSES):
595- if mp.target_branch != target_branch_lp:
596- continue
597- if mp.source_branch != source_branch_lp:
598- continue
599- return LaunchpadMergeProposal(mp)
600- raise NoMergeProposal()
601+ for mp in target_branch_lp.getMergeProposals(status=statuses):
602+ if mp.source_branch_link != source_branch_lp.self_link:
603+ continue
604+ yield LaunchpadMergeProposal(mp)
605 elif base_vcs == 'git':
606 (source_repo_lp, source_branch_lp) = (
607 self.lp_host._get_lp_git_ref_from_branch(source_branch))
608 (target_repo_lp, target_branch_lp) = (
609 self.lp_host._get_lp_git_ref_from_branch(target_branch))
610- for mp in target_branch_lp.getMergeProposals(
611- status=MERGE_PROPOSAL_STATUSES):
612+ for mp in target_branch_lp.getMergeProposals(status=statuses):
613 if (target_branch_lp.path != mp.target_git_path or
614 target_repo_lp != mp.target_git_repository or
615 source_branch_lp.path != mp.source_git_path or
616 source_repo_lp != mp.source_git_repository):
617 continue
618- return LaunchpadMergeProposal(mp)
619- raise NoMergeProposal()
620+ yield LaunchpadMergeProposal(mp)
621 else:
622 raise AssertionError('not a valid Launchpad URL')
623
624 def get_proposer(self, source_branch, target_branch):
625- (base_vcs, base_user, base_password, base_path, base_params) = (
626- self._split_url(target_branch.user_url))
627+ (base_vcs, base_user, base_password, base_path,
628+ base_params) = self._split_url(target_branch.user_url)
629 if base_vcs == 'bzr':
630 return LaunchpadBazaarMergeProposalBuilder(
631 self, source_branch, target_branch)
632@@ -329,6 +362,15 @@
633 else:
634 raise AssertionError('not a valid Launchpad URL')
635
636+ @classmethod
637+ def iter_instances(cls):
638+ yield cls()
639+
640+ def iter_my_proposals(self, status='open'):
641+ statuses = status_to_lp_mp_statuses(status)
642+ for mp in self.launchpad.me.getMergeProposals(status=statuses):
643+ yield LaunchpadMergeProposal(mp)
644+
645
646 def connect_launchpad(lp_instance='production'):
647 service = lp_registration.LaunchpadService(lp_instance=lp_instance)
648@@ -354,13 +396,16 @@
649 self.lp_host = lp_host
650 self.launchpad = lp_host.launchpad
651 self.source_branch = source_branch
652- self.source_branch_lp = self.launchpad.branches.getByUrl(url=source_branch.user_url)
653+ self.source_branch_lp = self.launchpad.branches.getByUrl(
654+ url=source_branch.user_url)
655 if target_branch is None:
656 self.target_branch_lp = self.source_branch_lp.get_target()
657- self.target_branch = _mod_branch.Branch.open(self.target_branch_lp.bzr_identity)
658+ self.target_branch = _mod_branch.Branch.open(
659+ self.target_branch_lp.bzr_identity)
660 else:
661 self.target_branch = target_branch
662- self.target_branch_lp = self.launchpad.branches.getByUrl(url=target_branch.user_url)
663+ self.target_branch_lp = self.launchpad.branches.getByUrl(
664+ url=target_branch.user_url)
665 self.commit_message = message
666 self.approve = approve
667 self.fixes = fixes
668@@ -414,7 +459,7 @@
669 _call_webservice(
670 mp.createComment,
671 vote=u'Approve',
672- subject='', # Use the default subject.
673+ subject='', # Use the default subject.
674 content=u"Rubberstamp! Proposer approves of own proposal.")
675 _call_webservice(mp.setStatus, status=u'Approved',
676 revid=self.source_branch.last_revision())
677@@ -478,13 +523,17 @@
678 self.lp_host = lp_host
679 self.launchpad = lp_host.launchpad
680 self.source_branch = source_branch
681- (self.source_repo_lp, self.source_branch_lp) = self.lp_host._get_lp_git_ref_from_branch(source_branch)
682+ (self.source_repo_lp,
683+ self.source_branch_lp) = self.lp_host._get_lp_git_ref_from_branch(
684+ source_branch)
685 if target_branch is None:
686 self.target_branch_lp = self.source_branch.get_target()
687- self.target_branch = _mod_branch.Branch.open(self.target_branch_lp.git_https_url)
688+ self.target_branch = _mod_branch.Branch.open(
689+ self.target_branch_lp.git_https_url)
690 else:
691 self.target_branch = target_branch
692- (self.target_repo_lp, self.target_branch_lp) = self.lp_host._get_lp_git_ref_from_branch(target_branch)
693+ (self.target_repo_lp, self.target_branch_lp) = (
694+ self.lp_host._get_lp_git_ref_from_branch(target_branch))
695 self.commit_message = message
696 self.approve = approve
697 self.fixes = fixes
698@@ -538,7 +587,7 @@
699 _call_webservice(
700 mp.createComment,
701 vote=u'Approve',
702- subject='', # Use the default subject.
703+ subject='', # Use the default subject.
704 content=u"Rubberstamp! Proposer approves of own proposal.")
705 _call_webservice(
706 mp.setStatus, status=u'Approved',
707@@ -550,7 +599,8 @@
708 if labels:
709 raise LabelsUnsupported()
710 if prerequisite_branch is not None:
711- (prereq_repo_lp, prereq_branch_lp) = self.lp_host._get_lp_git_ref_from_branch(prerequisite_branch)
712+ (prereq_repo_lp, prereq_branch_lp) = (
713+ self.lp_host._get_lp_git_ref_from_branch(prerequisite_branch))
714 else:
715 prereq_branch_lp = None
716 if reviewers is None:
717
718=== modified file 'breezy/plugins/propose/propose.py'
719--- breezy/plugins/propose/propose.py 2019-01-01 22:31:13 +0000
720+++ breezy/plugins/propose/propose.py 2019-01-08 21:33:41 +0000
721@@ -43,14 +43,6 @@
722 self.url = url
723
724
725-class NoMergeProposal(errors.BzrError):
726-
727- _fmt = "No merge proposal exists."
728-
729- def __init__(self):
730- errors.BzrError.__init__(self)
731-
732-
733 class UnsupportedHoster(errors.BzrError):
734
735 _fmt = "No supported hoster for %(branch)s."
736@@ -108,6 +100,14 @@
737 """Set the description of the merge proposal."""
738 raise NotImplementedError(self.set_description)
739
740+ def get_source_branch_url(self):
741+ """Return the source branch."""
742+ raise NotImplementedError(self.get_source_branch_url)
743+
744+ def get_target_branch_url(self):
745+ """Return the target branch."""
746+ raise NotImplementedError(self.get_target_branch_url)
747+
748 def close(self):
749 """Close the merge proposal (without merging it)."""
750 raise NotImplementedError(self.close)
751@@ -192,15 +192,15 @@
752 """
753 raise NotImplementedError(self.get_proposer)
754
755- def get_proposal(self, source_branch, target_branch):
756+ def iter_proposals(self, source_branch, target_branch, status='open'):
757 """Get a merge proposal for a specified branch tuple.
758
759 :param source_branch: Source branch
760 :param target_branch: Target branch
761- :raise NoMergeProposal: if no merge proposal can be found
762- :return: A MergeProposal object
763+ :param status: Status of proposals to iterate over
764+ :return: Iterate over MergeProposal object
765 """
766- raise NotImplementedError(self.get_proposal)
767+ raise NotImplementedError(self.iter_proposals)
768
769 def hosts(self, branch):
770 """Return true if this hoster hosts given branch."""
771@@ -212,7 +212,23 @@
772 raise NotImplementedError(cls.probe)
773
774 # TODO(jelmer): Some way of cleaning up old branch proposals/branches
775- # TODO(jelmer): Some way of checking up on outstanding merge proposals
776+
777+ def iter_my_proposals(self, status='open'):
778+ """Iterate over the proposals created by the currently logged in user.
779+
780+ :param status: Only yield proposals with this status
781+ (one of: 'open', 'closed', 'merged', 'all')
782+ :return: Iterator over MergeProposal objects
783+ """
784+ raise NotImplementedError(self.iter_my_proposals)
785+
786+ @classmethod
787+ def iter_instances(cls):
788+ """Iterate instances.
789+
790+ :return: Iterator over Hoster instances
791+ """
792+ raise NotImplementedError(cls.iter_instances)
793
794
795 def get_hoster(branch, possible_hosters=None):

Subscribers

People subscribed via source and target branches