/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/plugins/propose/cmds.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-07-28 02:47:10 UTC
  • mfrom: (7519.1.1 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200728024710-a2ylds219f1lsl62
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/merge-3.1/+merge/388173

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2018 Jelmer Vernooij <jelmer@jelmer.uk>
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
"""Propose command implementations."""
 
18
 
 
19
from io import StringIO
 
20
 
 
21
from ... import (
 
22
    branch as _mod_branch,
 
23
    controldir,
 
24
    errors,
 
25
    log as _mod_log,
 
26
    missing as _mod_missing,
 
27
    msgeditor,
 
28
    urlutils,
 
29
    )
 
30
from ...i18n import gettext
 
31
from ...commands import Command
 
32
from ...option import (
 
33
    ListOption,
 
34
    Option,
 
35
    RegistryOption,
 
36
    )
 
37
from ...trace import note
 
38
from ... import (
 
39
    propose as _mod_propose,
 
40
    )
 
41
 
 
42
 
 
43
def branch_name(branch):
 
44
    if branch.name:
 
45
        return branch.name
 
46
    return urlutils.basename(branch.user_url)
 
47
 
 
48
 
 
49
def _check_already_merged(branch, target):
 
50
    # TODO(jelmer): Check entire ancestry rather than just last revision?
 
51
    if branch.last_revision() == target.last_revision():
 
52
        raise errors.CommandError(gettext(
 
53
            'All local changes are already present in target.'))
 
54
 
 
55
 
 
56
class cmd_publish_derived(Command):
 
57
    __doc__ = """Publish a derived branch.
 
58
 
 
59
    Try to create a public copy of a local branch on a hosting site,
 
60
    derived from the specified base branch.
 
61
 
 
62
    Reasonable defaults are picked for owner name, branch name and project
 
63
    name, but they can also be overridden from the command-line.
 
64
    """
 
65
 
 
66
    takes_options = [
 
67
        'directory',
 
68
        Option('owner', help='Owner of the new remote branch.', type=str),
 
69
        Option('project', help='Project name for the new remote branch.',
 
70
               type=str),
 
71
        Option('name', help='Name of the new remote branch.', type=str),
 
72
        Option('no-allow-lossy',
 
73
               help='Allow fallback to lossy push, if necessary.'),
 
74
        Option('overwrite', help="Overwrite existing commits."),
 
75
        ]
 
76
    takes_args = ['submit_branch?']
 
77
 
 
78
    def run(self, submit_branch=None, owner=None, name=None, project=None,
 
79
            no_allow_lossy=False, overwrite=False, directory='.'):
 
80
        local_branch = _mod_branch.Branch.open_containing(directory)[0]
 
81
        self.add_cleanup(local_branch.lock_write().unlock)
 
82
        if submit_branch is None:
 
83
            submit_branch = local_branch.get_submit_branch()
 
84
            note(gettext('Using submit branch %s') % submit_branch)
 
85
        if submit_branch is None:
 
86
            submit_branch = local_branch.get_parent()
 
87
            note(gettext('Using parent branch %s') % submit_branch)
 
88
        submit_branch = _mod_branch.Branch.open(submit_branch)
 
89
        _check_already_merged(local_branch, submit_branch)
 
90
        if name is None:
 
91
            name = branch_name(local_branch)
 
92
        hoster = _mod_propose.get_hoster(submit_branch)
 
93
        remote_branch, public_url = hoster.publish_derived(
 
94
            local_branch, submit_branch, name=name, project=project,
 
95
            owner=owner, allow_lossy=not no_allow_lossy,
 
96
            overwrite=overwrite)
 
97
        local_branch.set_push_location(remote_branch.user_url)
 
98
        local_branch.set_public_branch(public_url)
 
99
        local_branch.set_submit_branch(submit_branch.user_url)
 
100
        note(gettext("Pushed to %s") % public_url)
 
101
 
 
102
 
 
103
def summarize_unmerged(local_branch, remote_branch, target,
 
104
                       prerequisite_branch=None):
 
105
    """Generate a text description of the unmerged revisions in branch.
 
106
 
 
107
    :param branch: The proposed branch
 
108
    :param target: Target branch
 
109
    :param prerequisite_branch: Optional prerequisite branch
 
110
    :return: A string
 
111
    """
 
112
    log_format = _mod_log.log_formatter_registry.get_default(local_branch)
 
113
    to_file = StringIO()
 
114
    lf = log_format(to_file=to_file, show_ids=False, show_timezone='original')
 
115
    if prerequisite_branch:
 
116
        local_extra = _mod_missing.find_unmerged(
 
117
            remote_branch, prerequisite_branch, restrict='local')[0]
 
118
    else:
 
119
        local_extra = _mod_missing.find_unmerged(
 
120
            remote_branch, target, restrict='local')[0]
 
121
 
 
122
    if remote_branch.supports_tags():
 
123
        rev_tag_dict = remote_branch.tags.get_reverse_tag_dict()
 
124
    else:
 
125
        rev_tag_dict = {}
 
126
 
 
127
    for revision in _mod_missing.iter_log_revisions(
 
128
            local_extra, local_branch.repository, False, rev_tag_dict):
 
129
        lf.log_revision(revision)
 
130
    return to_file.getvalue()
 
131
 
 
132
 
 
133
class cmd_propose_merge(Command):
 
134
    __doc__ = """Propose a branch for merging.
 
135
 
 
136
    This command creates a merge proposal for the local
 
137
    branch to the target branch. The format of the merge
 
138
    proposal depends on the submit branch.
 
139
    """
 
140
 
 
141
    takes_options = [
 
142
        'directory',
 
143
        RegistryOption(
 
144
            'hoster',
 
145
            help='Use the hoster.',
 
146
            lazy_registry=('breezy.plugins.propose.propose', 'hosters')),
 
147
        ListOption('reviewers', short_name='R', type=str,
 
148
                   help='Requested reviewers.'),
 
149
        Option('name', help='Name of the new remote branch.', type=str),
 
150
        Option('description', help='Description of the change.', type=str),
 
151
        Option('prerequisite', help='Prerequisite branch.', type=str),
 
152
        Option('wip', help='Mark merge request as work-in-progress'),
 
153
        Option(
 
154
            'commit-message',
 
155
            help='Set commit message for merge, if supported', type=str),
 
156
        ListOption('labels', short_name='l', type=str,
 
157
                   help='Labels to apply.'),
 
158
        Option('no-allow-lossy',
 
159
               help='Allow fallback to lossy push, if necessary.'),
 
160
        Option('allow-collaboration',
 
161
               help='Allow collaboration from target branch maintainer(s)'),
 
162
        Option('allow-empty',
 
163
               help='Do not prevent empty merge proposals.'),
 
164
        ]
 
165
    takes_args = ['submit_branch?']
 
166
 
 
167
    aliases = ['propose']
 
168
 
 
169
    def run(self, submit_branch=None, directory='.', hoster=None,
 
170
            reviewers=None, name=None, no_allow_lossy=False, description=None,
 
171
            labels=None, prerequisite=None, commit_message=None, wip=False,
 
172
            allow_collaboration=False, allow_empty=False):
 
173
        tree, branch, relpath = (
 
174
            controldir.ControlDir.open_containing_tree_or_branch(directory))
 
175
        if submit_branch is None:
 
176
            submit_branch = branch.get_submit_branch()
 
177
        if submit_branch is None:
 
178
            submit_branch = branch.get_parent()
 
179
        if submit_branch is None:
 
180
            raise errors.CommandError(
 
181
                gettext("No target location specified or remembered"))
 
182
        target = _mod_branch.Branch.open(submit_branch)
 
183
        if not allow_empty:
 
184
            _check_already_merged(branch, target)
 
185
        if hoster is None:
 
186
            hoster = _mod_propose.get_hoster(target)
 
187
        else:
 
188
            hoster = hoster.probe(target)
 
189
        if name is None:
 
190
            name = branch_name(branch)
 
191
        remote_branch, public_branch_url = hoster.publish_derived(
 
192
            branch, target, name=name, allow_lossy=not no_allow_lossy)
 
193
        branch.set_push_location(remote_branch.user_url)
 
194
        branch.set_submit_branch(target.user_url)
 
195
        note(gettext('Published branch to %s') % public_branch_url)
 
196
        if prerequisite is not None:
 
197
            prerequisite_branch = _mod_branch.Branch.open(prerequisite)
 
198
        else:
 
199
            prerequisite_branch = None
 
200
        proposal_builder = hoster.get_proposer(remote_branch, target)
 
201
        if description is None:
 
202
            body = proposal_builder.get_initial_body()
 
203
            info = proposal_builder.get_infotext()
 
204
            info += "\n\n" + summarize_unmerged(
 
205
                branch, remote_branch, target, prerequisite_branch)
 
206
            description = msgeditor.edit_commit_message(
 
207
                info, start_message=body)
 
208
        try:
 
209
            proposal = proposal_builder.create_proposal(
 
210
                description=description, reviewers=reviewers,
 
211
                prerequisite_branch=prerequisite_branch, labels=labels,
 
212
                commit_message=commit_message,
 
213
                work_in_progress=wip, allow_collaboration=allow_collaboration)
 
214
        except _mod_propose.MergeProposalExists as e:
 
215
            note(gettext('There is already a branch merge proposal: %s'), e.url)
 
216
        else:
 
217
            note(gettext('Merge proposal created: %s') % proposal.url)
 
218
 
 
219
 
 
220
class cmd_find_merge_proposal(Command):
 
221
    __doc__ = """Find a merge proposal.
 
222
 
 
223
    """
 
224
 
 
225
    takes_options = ['directory']
 
226
    takes_args = ['submit_branch?']
 
227
    aliases = ['find-proposal']
 
228
 
 
229
    def run(self, directory='.', submit_branch=None):
 
230
        tree, branch, relpath = controldir.ControlDir.open_containing_tree_or_branch(
 
231
            directory)
 
232
        public_location = branch.get_public_branch()
 
233
        if public_location:
 
234
            branch = _mod_branch.Branch.open(public_location)
 
235
        if submit_branch is None:
 
236
            submit_branch = branch.get_submit_branch()
 
237
        if submit_branch is None:
 
238
            submit_branch = branch.get_parent()
 
239
        if submit_branch is None:
 
240
            raise errors.CommandError(
 
241
                gettext("No target location specified or remembered"))
 
242
        else:
 
243
            target = _mod_branch.Branch.open(submit_branch)
 
244
        hoster = _mod_propose.get_hoster(branch)
 
245
        for mp in hoster.iter_proposals(branch, target):
 
246
            self.outf.write(gettext('Merge proposal: %s\n') % mp.url)
 
247
 
 
248
 
 
249
class cmd_my_merge_proposals(Command):
 
250
    __doc__ = """List all merge proposals owned by the logged-in user.
 
251
 
 
252
    """
 
253
 
 
254
    hidden = True
 
255
 
 
256
    takes_options = [
 
257
        'verbose',
 
258
        RegistryOption.from_kwargs(
 
259
            'status',
 
260
            title='Proposal Status',
 
261
            help='Only include proposals with specified status.',
 
262
            value_switches=True,
 
263
            enum_switch=True,
 
264
            all='All merge proposals',
 
265
            open='Open merge proposals',
 
266
            merged='Merged merge proposals',
 
267
            closed='Closed merge proposals')]
 
268
 
 
269
    def run(self, status='open', verbose=False):
 
270
        for instance in _mod_propose.iter_hoster_instances():
 
271
            for mp in instance.iter_my_proposals(status=status):
 
272
                self.outf.write('%s\n' % mp.url)
 
273
                if verbose:
 
274
                    self.outf.write(
 
275
                        '(Merging %s into %s)\n' %
 
276
                        (mp.get_source_branch_url(),
 
277
                         mp.get_target_branch_url()))
 
278
                    description = mp.get_description()
 
279
                    if description:
 
280
                        self.outf.writelines(
 
281
                            ['\t%s\n' % l
 
282
                             for l in description.splitlines()])
 
283
                    self.outf.write('\n')
 
284
 
 
285
 
 
286
class cmd_land_merge_proposal(Command):
 
287
    __doc__ = """Land a merge proposal."""
 
288
 
 
289
    takes_args = ['url']
 
290
    takes_options = [
 
291
        Option('message', help='Commit message to use.', type=str)]
 
292
 
 
293
    def run(self, url, message=None):
 
294
        proposal = _mod_propose.get_proposal_by_url(url)
 
295
        proposal.merge(commit_message=message)
 
296
 
 
297
 
 
298
class cmd_hosters(Command):
 
299
    __doc__ = """List all known hosting sites and user details."""
 
300
 
 
301
    hidden = True
 
302
 
 
303
    def run(self):
 
304
        for instance in _mod_propose.iter_hoster_instances():
 
305
            current_user = instance.get_current_user()
 
306
            self.outf.write(
 
307
                gettext('%s (%s) - user: %s (%s)\n') % (
 
308
                    instance.name, instance.base_url,
 
309
                    current_user, instance.get_user_url(current_user)))