1
# Copyright (C) 2018 Breezy Developers
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
"""Helper functions for proposing merges."""
19
from __future__ import absolute_import
28
class NoSuchProject(errors.BzrError):
30
_fmt = "Project does not exist: %(project)s."
32
def __init__(self, project):
33
errors.BzrError.__init__(self)
34
self.project = project
37
class MergeProposalExists(errors.BzrError):
39
_fmt = "A merge proposal already exists: %(url)s."
41
def __init__(self, url):
42
errors.BzrError.__init__(self)
46
class NoMergeProposal(errors.BzrError):
48
_fmt = "No merge proposal exists."
51
errors.BzrError.__init__(self)
54
class UnsupportedHoster(errors.BzrError):
56
_fmt = "No supported hoster for %(branch)s."
58
def __init__(self, branch):
59
errors.BzrError.__init__(self)
63
class ProposeMergeHooks(hooks.Hooks):
64
"""Hooks for proposing a merge on Launchpad."""
67
hooks.Hooks.__init__(self, __name__, "Proposer.hooks")
70
"Return the prerequisite branch for proposing as merge.", (3, 0))
72
'merge_proposal_body',
73
"Return an initial body for the merge proposal message.", (3, 0))
76
class LabelsUnsupported(errors.BzrError):
77
"""Labels not supported by this hoster."""
79
_fmt = "Labels are not supported by %(hoster)r."
81
def __init__(self, hoster):
82
errors.BzrError.__init__(self)
86
class PrerequisiteBranchUnsupported(errors.BzrError):
87
"""Prerequisite branch not supported by this hoster."""
89
def __init__(self, hoster):
90
errors.BzrError.__init__(self)
94
class MergeProposal(object):
97
:ivar url: URL for the merge proposal
100
def __init__(self, url=None):
103
def get_description(self):
104
"""Get the description of the merge proposal."""
105
raise NotImplementedError(self.get_description)
107
def set_description(self, description):
108
"""Set the description of the merge proposal."""
109
raise NotImplementedError(self.set_description)
112
"""Close the merge proposal (without merging it)."""
113
raise NotImplementedError(self.close)
116
"""Check whether this merge proposal has been merged."""
117
raise NotImplementedError(self.is_merged)
120
class MergeProposalBuilder(object):
121
"""Merge proposal creator.
123
:param source_branch: Branch to propose for merging
124
:param target_branch: Target branch
127
hooks = ProposeMergeHooks()
129
def __init__(self, source_branch, target_branch):
130
self.source_branch = source_branch
131
self.target_branch = target_branch
133
def get_initial_body(self):
134
"""Get a body for the proposal for the user to modify.
136
:return: a str or None.
138
raise NotImplementedError(self.get_initial_body)
140
def get_infotext(self):
141
"""Determine the initial comment for the merge proposal.
143
raise NotImplementedError(self.get_infotext)
145
def create_proposal(self, description, reviewers=None, labels=None,
146
prerequisite_branch=None):
147
"""Create a proposal to merge a branch for merging.
149
:param description: Description for the merge proposal
150
:param reviewers: Optional list of people to ask reviews from
151
:param labels: Labels to attach to the proposal
152
:param prerequisite_branch: Optional prerequisite branch
153
:return: A `MergeProposal` object
155
raise NotImplementedError(self.create_proposal)
158
class Hoster(object):
159
"""A hosting site manager.
162
supports_merge_proposal_labels = None
164
def publish_derived(self, new_branch, base_branch, name, project=None,
165
owner=None, revision_id=None, overwrite=False,
167
"""Publish a branch to the site, derived from base_branch.
169
:param base_branch: branch to derive the new branch from
170
:param new_branch: branch to publish
171
:return: resulting branch, public URL
173
raise NotImplementedError(self.publish)
175
def get_derived_branch(self, base_branch, name, project=None, owner=None):
176
"""Get a derived branch ('a fork').
178
raise NotImplementedError(self.get_derived_branch)
180
def get_push_url(self, branch):
181
"""Get the push URL for a branch."""
182
raise NotImplementedError(self.get_push_url)
184
def get_proposer(self, source_branch, target_branch):
185
"""Get a merge proposal creator.
187
:note: source_branch does not have to be hosted by the hoster.
189
:param source_branch: Source branch
190
:param target_branch: Target branch
191
:return: A MergeProposalBuilder object
193
raise NotImplementedError(self.get_proposer)
195
def get_proposal(self, source_branch, target_branch):
196
"""Get a merge proposal for a specified branch tuple.
198
:param source_branch: Source branch
199
:param target_branch: Target branch
200
:raise NoMergeProposal: if no merge proposal can be found
201
:return: A MergeProposal object
203
raise NotImplementedError(self.get_proposal)
205
def hosts(self, branch):
206
"""Return true if this hoster hosts given branch."""
207
raise NotImplementedError(self.hosts)
210
def probe(cls, branch):
211
"""Create a Hoster object if this hoster knows about a branch."""
212
raise NotImplementedError(cls.probe)
214
# TODO(jelmer): Some way of cleaning up old branch proposals/branches
215
# TODO(jelmer): Some way of checking up on outstanding merge proposals
218
def get_hoster(branch, possible_hosters=None):
219
"""Find the hoster for a branch."""
221
for hoster in possible_hosters:
222
if hoster.hosts(branch):
224
for name, hoster_cls in hosters.items():
226
hoster = hoster_cls.probe(branch)
227
except UnsupportedHoster:
230
if possible_hosters is not None:
231
possible_hosters.append(hoster)
233
raise UnsupportedHoster(branch)
236
hosters = registry.Registry()
237
hosters.register_lazy(
238
"launchpad", "breezy.plugins.propose.launchpad",
240
hosters.register_lazy(
241
"github", "breezy.plugins.propose.github",
243
hosters.register_lazy(
244
"gitlab", "breezy.plugins.propose.gitlabs",