/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: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

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