/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-09-02 16:35:18 UTC
  • mto: (7490.40.109 work)
  • mto: This revision was merged to the branch mainline in revision 7526.
  • Revision ID: jelmer@jelmer.uk-20200902163518-sy9f4unbboljphgu
Handle duplicate directories entries for git.

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, warning
 
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.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
        Option('overwrite', help="Overwrite existing commits."),
 
168
        ]
 
169
    takes_args = ['submit_branch?']
 
170
 
 
171
    aliases = ['propose']
 
172
 
 
173
    def run(self, submit_branch=None, directory='.', hoster=None,
 
174
            reviewers=None, name=None, no_allow_lossy=False, description=None,
 
175
            labels=None, prerequisite=None, commit_message=None, wip=False,
 
176
            allow_collaboration=False, allow_empty=False, overwrite=False):
 
177
        tree, branch, relpath = (
 
178
            controldir.ControlDir.open_containing_tree_or_branch(directory))
 
179
        if submit_branch is None:
 
180
            submit_branch = branch.get_submit_branch()
 
181
        if submit_branch is None:
 
182
            submit_branch = branch.get_parent()
 
183
        if submit_branch is None:
 
184
            raise errors.CommandError(
 
185
                gettext("No target location specified or remembered"))
 
186
        target = _mod_branch.Branch.open(submit_branch)
 
187
        if not allow_empty:
 
188
            _check_already_merged(branch, target)
 
189
        if hoster is None:
 
190
            hoster = _mod_propose.get_hoster(target)
 
191
        else:
 
192
            hoster = hoster.probe(target)
 
193
        if name is None:
 
194
            name = branch_name(branch)
 
195
        remote_branch, public_branch_url = hoster.publish_derived(
 
196
            branch, target, name=name, allow_lossy=not no_allow_lossy,
 
197
            overwrite=overwrite)
 
198
        branch.set_push_location(remote_branch.user_url)
 
199
        branch.set_submit_branch(target.user_url)
 
200
        note(gettext('Published branch to %s') % public_branch_url)
 
201
        if prerequisite is not None:
 
202
            prerequisite_branch = _mod_branch.Branch.open(prerequisite)
 
203
        else:
 
204
            prerequisite_branch = None
 
205
        proposal_builder = hoster.get_proposer(remote_branch, target)
 
206
        if description is None:
 
207
            body = proposal_builder.get_initial_body()
 
208
            info = proposal_builder.get_infotext()
 
209
            info += "\n\n" + summarize_unmerged(
 
210
                branch, remote_branch, target, prerequisite_branch)
 
211
            description = msgeditor.edit_commit_message(
 
212
                info, start_message=body)
 
213
        try:
 
214
            proposal = proposal_builder.create_proposal(
 
215
                description=description, reviewers=reviewers,
 
216
                prerequisite_branch=prerequisite_branch, labels=labels,
 
217
                commit_message=commit_message,
 
218
                work_in_progress=wip, allow_collaboration=allow_collaboration)
 
219
        except _mod_propose.MergeProposalExists as e:
 
220
            note(gettext('There is already a branch merge proposal: %s'), e.url)
 
221
        else:
 
222
            note(gettext('Merge proposal created: %s') % proposal.url)
 
223
 
 
224
 
 
225
class cmd_find_merge_proposal(Command):
 
226
    __doc__ = """Find a merge proposal.
 
227
 
 
228
    """
 
229
 
 
230
    takes_options = ['directory']
 
231
    takes_args = ['submit_branch?']
 
232
    aliases = ['find-proposal']
 
233
 
 
234
    def run(self, directory='.', submit_branch=None):
 
235
        tree, branch, relpath = controldir.ControlDir.open_containing_tree_or_branch(
 
236
            directory)
 
237
        public_location = branch.get_public_branch()
 
238
        if public_location:
 
239
            branch = _mod_branch.Branch.open(public_location)
 
240
        if submit_branch is None:
 
241
            submit_branch = branch.get_submit_branch()
 
242
        if submit_branch is None:
 
243
            submit_branch = branch.get_parent()
 
244
        if submit_branch is None:
 
245
            raise errors.CommandError(
 
246
                gettext("No target location specified or remembered"))
 
247
        else:
 
248
            target = _mod_branch.Branch.open(submit_branch)
 
249
        hoster = _mod_propose.get_hoster(branch)
 
250
        for mp in hoster.iter_proposals(branch, target):
 
251
            self.outf.write(gettext('Merge proposal: %s\n') % mp.url)
 
252
 
 
253
 
 
254
class cmd_my_merge_proposals(Command):
 
255
    __doc__ = """List all merge proposals owned by the logged-in user.
 
256
 
 
257
    """
 
258
 
 
259
    hidden = True
 
260
 
 
261
    takes_args = ['base-url?']
 
262
    takes_options = [
 
263
        'verbose',
 
264
        RegistryOption.from_kwargs(
 
265
            'status',
 
266
            title='Proposal Status',
 
267
            help='Only include proposals with specified status.',
 
268
            value_switches=True,
 
269
            enum_switch=True,
 
270
            all='All merge proposals',
 
271
            open='Open merge proposals',
 
272
            merged='Merged merge proposals',
 
273
            closed='Closed merge proposals'),
 
274
        RegistryOption(
 
275
            'hoster',
 
276
            help='Use the hoster.',
 
277
            lazy_registry=('breezy.propose', 'hosters')),
 
278
        ]
 
279
 
 
280
    def run(self, status='open', verbose=False, hoster=None, base_url=None):
 
281
 
 
282
        for instance in _mod_propose.iter_hoster_instances(hoster=hoster):
 
283
            if base_url is not None and instance.base_url != base_url:
 
284
                continue
 
285
            try:
 
286
                for mp in instance.iter_my_proposals(status=status):
 
287
                    self.outf.write('%s\n' % mp.url)
 
288
                    if verbose:
 
289
                        source_branch_url = mp.get_source_branch_url()
 
290
                        if source_branch_url:
 
291
                            self.outf.write(
 
292
                                '(Merging %s into %s)\n' %
 
293
                                (source_branch_url,
 
294
                                 mp.get_target_branch_url()))
 
295
                        else:
 
296
                            self.outf.write(
 
297
                                '(Merging into %s)\n' %
 
298
                                mp.get_target_branch_url())
 
299
                        description = mp.get_description()
 
300
                        if description:
 
301
                            self.outf.writelines(
 
302
                                ['\t%s\n' % l
 
303
                                 for l in description.splitlines()])
 
304
                        self.outf.write('\n')
 
305
            except _mod_propose.HosterLoginRequired as e:
 
306
                warning('Skipping %r, login required.', instance)
 
307
 
 
308
 
 
309
class cmd_land_merge_proposal(Command):
 
310
    __doc__ = """Land a merge proposal."""
 
311
 
 
312
    takes_args = ['url']
 
313
    takes_options = [
 
314
        Option('message', help='Commit message to use.', type=str)]
 
315
 
 
316
    def run(self, url, message=None):
 
317
        proposal = _mod_propose.get_proposal_by_url(url)
 
318
        proposal.merge(commit_message=message)
 
319
 
 
320
 
 
321
class cmd_hosters(Command):
 
322
    __doc__ = """List all known hosting sites and user details."""
 
323
 
 
324
    hidden = True
 
325
 
 
326
    def run(self):
 
327
        for instance in _mod_propose.iter_hoster_instances():
 
328
            current_user = instance.get_current_user()
 
329
            if current_user is not None:
 
330
                current_user_url = instance.get_user_url(current_user)
 
331
                if current_user_url is not None:
 
332
                    self.outf.write(
 
333
                        gettext('%s (%s) - user: %s (%s)\n') % (
 
334
                            instance.name, instance.base_url,
 
335
                            current_user, current_user_url))
 
336
                else:
 
337
                    self.outf.write(
 
338
                        gettext('%s (%s) - user: %s\n') % (
 
339
                            instance.name, instance.base_url,
 
340
                            current_user))
 
341
            else:
 
342
                self.outf.write(
 
343
                    gettext('%s (%s) - not logged in\n') % (
 
344
                        instance.name, instance.base_url))