/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: Jelmer Vernooij
  • Date: 2020-07-18 15:20:23 UTC
  • mto: (7490.40.61 work)
  • mto: This revision was merged to the branch mainline in revision 7519.
  • Revision ID: jelmer@jelmer.uk-20200718152023-cabh92o24ke217te
Ignore missing revs.

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