/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: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

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
 
class cmd_publish_derived(Command):
50
 
    __doc__ = """Publish a derived branch.
51
 
 
52
 
    Try to create a public copy of a local branch on a hosting site,
53
 
    derived from the specified base branch.
54
 
 
55
 
    Reasonable defaults are picked for owner name, branch name and project
56
 
    name, but they can also be overridden from the command-line.
57
 
    """
58
 
 
59
 
    takes_options = [
60
 
        'directory',
61
 
        Option('owner', help='Owner of the new remote branch.', type=str),
62
 
        Option('project', help='Project name for the new remote branch.',
63
 
               type=str),
64
 
        Option('name', help='Name of the new remote branch.', type=str),
65
 
        Option('no-allow-lossy',
66
 
               help='Allow fallback to lossy push, if necessary.'),
67
 
        Option('overwrite', help="Overwrite existing commits."),
68
 
        ]
69
 
    takes_args = ['submit_branch?']
70
 
 
71
 
    def run(self, submit_branch=None, owner=None, name=None, project=None,
72
 
            no_allow_lossy=False, overwrite=False, directory='.'):
73
 
        local_branch = _mod_branch.Branch.open_containing(directory)[0]
74
 
        self.add_cleanup(local_branch.lock_write().unlock)
75
 
        if submit_branch is None:
76
 
            submit_branch = local_branch.get_submit_branch()
77
 
            note(gettext('Using submit branch %s') % submit_branch)
78
 
        if submit_branch is None:
79
 
            submit_branch = local_branch.get_parent()
80
 
            note(gettext('Using parent branch %s') % submit_branch)
81
 
        submit_branch = _mod_branch.Branch.open(submit_branch)
82
 
        if name is None:
83
 
            name = branch_name(local_branch)
84
 
        hoster = _mod_propose.get_hoster(submit_branch)
85
 
        remote_branch, public_url = hoster.publish_derived(
86
 
            local_branch, submit_branch, name=name, project=project,
87
 
            owner=owner, allow_lossy=not no_allow_lossy,
88
 
            overwrite=overwrite)
89
 
        local_branch.set_push_location(remote_branch.user_url)
90
 
        local_branch.set_public_branch(public_url)
91
 
        local_branch.set_submit_branch(submit_branch.user_url)
92
 
        note(gettext("Pushed to %s") % public_url)
93
 
 
94
 
 
95
 
def summarize_unmerged(local_branch, remote_branch, target,
96
 
                       prerequisite_branch=None):
97
 
    """Generate a text description of the unmerged revisions in branch.
98
 
 
99
 
    :param branch: The proposed branch
100
 
    :param target: Target branch
101
 
    :param prerequisite_branch: Optional prerequisite branch
102
 
    :return: A string
103
 
    """
104
 
    log_format = _mod_log.log_formatter_registry.get_default(local_branch)
105
 
    to_file = StringIO()
106
 
    lf = log_format(to_file=to_file, show_ids=False, show_timezone='original')
107
 
    if prerequisite_branch:
108
 
        local_extra = _mod_missing.find_unmerged(
109
 
            remote_branch, prerequisite_branch, restrict='local')[0]
110
 
    else:
111
 
        local_extra = _mod_missing.find_unmerged(
112
 
            remote_branch, target, restrict='local')[0]
113
 
 
114
 
    if remote_branch.supports_tags():
115
 
        rev_tag_dict = remote_branch.tags.get_reverse_tag_dict()
116
 
    else:
117
 
        rev_tag_dict = {}
118
 
 
119
 
    for revision in _mod_missing.iter_log_revisions(
120
 
            local_extra, local_branch.repository, False, rev_tag_dict):
121
 
        lf.log_revision(revision)
122
 
    return to_file.getvalue()
123
 
 
124
 
 
125
 
class cmd_propose_merge(Command):
126
 
    __doc__ = """Propose a branch for merging.
127
 
 
128
 
    This command creates a merge proposal for the local
129
 
    branch to the target branch. The format of the merge
130
 
    proposal depends on the submit branch.
131
 
    """
132
 
 
133
 
    takes_options = [
134
 
        'directory',
135
 
        RegistryOption(
136
 
            'hoster',
137
 
            help='Use the hoster.',
138
 
            lazy_registry=('breezy.plugins.propose.propose', 'hosters')),
139
 
        ListOption('reviewers', short_name='R', type=str,
140
 
                   help='Requested reviewers.'),
141
 
        Option('name', help='Name of the new remote branch.', type=str),
142
 
        Option('description', help='Description of the change.', type=str),
143
 
        Option('prerequisite', help='Prerequisite branch.', type=str),
144
 
        Option('wip', help='Mark merge request as work-in-progress'),
145
 
        Option(
146
 
            'commit-message',
147
 
            help='Set commit message for merge, if supported', type=str),
148
 
        ListOption('labels', short_name='l', type=str,
149
 
                   help='Labels to apply.'),
150
 
        Option('no-allow-lossy',
151
 
               help='Allow fallback to lossy push, if necessary.'),
152
 
        ]
153
 
    takes_args = ['submit_branch?']
154
 
 
155
 
    aliases = ['propose']
156
 
 
157
 
    def run(self, submit_branch=None, directory='.', hoster=None,
158
 
            reviewers=None, name=None, no_allow_lossy=False, description=None,
159
 
            labels=None, prerequisite=None, commit_message=None, wip=False):
160
 
        tree, branch, relpath = (
161
 
            controldir.ControlDir.open_containing_tree_or_branch(directory))
162
 
        if submit_branch is None:
163
 
            submit_branch = branch.get_submit_branch()
164
 
        if submit_branch is None:
165
 
            submit_branch = branch.get_parent()
166
 
        if submit_branch is None:
167
 
            raise errors.BzrCommandError(
168
 
                gettext("No target location specified or remembered"))
169
 
        else:
170
 
            target = _mod_branch.Branch.open(submit_branch)
171
 
        if hoster is None:
172
 
            hoster = _mod_propose.get_hoster(target)
173
 
        else:
174
 
            hoster = hoster.probe(target)
175
 
        if name is None:
176
 
            name = branch_name(branch)
177
 
        remote_branch, public_branch_url = hoster.publish_derived(
178
 
            branch, target, name=name, allow_lossy=not no_allow_lossy)
179
 
        branch.set_push_location(remote_branch.user_url)
180
 
        branch.set_submit_branch(target.user_url)
181
 
        note(gettext('Published branch to %s') % public_branch_url)
182
 
        if prerequisite is not None:
183
 
            prerequisite_branch = _mod_branch.Branch.open(prerequisite)
184
 
        else:
185
 
            prerequisite_branch = None
186
 
        proposal_builder = hoster.get_proposer(remote_branch, target)
187
 
        if description is None:
188
 
            body = proposal_builder.get_initial_body()
189
 
            info = proposal_builder.get_infotext()
190
 
            info += "\n\n" + summarize_unmerged(
191
 
                branch, remote_branch, target, prerequisite_branch)
192
 
            description = msgeditor.edit_commit_message(
193
 
                info, start_message=body)
194
 
        try:
195
 
            proposal = proposal_builder.create_proposal(
196
 
                description=description, reviewers=reviewers,
197
 
                prerequisite_branch=prerequisite_branch, labels=labels,
198
 
                commit_message=commit_message,
199
 
                work_in_progress=wip)
200
 
        except _mod_propose.MergeProposalExists as e:
201
 
            note(gettext('There is already a branch merge proposal: %s'), e.url)
202
 
        else:
203
 
            note(gettext('Merge proposal created: %s') % proposal.url)
204
 
 
205
 
 
206
 
class cmd_find_merge_proposal(Command):
207
 
    __doc__ = """Find a merge proposal.
208
 
 
209
 
    """
210
 
 
211
 
    takes_options = ['directory']
212
 
    takes_args = ['submit_branch?']
213
 
    aliases = ['find-proposal']
214
 
 
215
 
    def run(self, directory='.', submit_branch=None):
216
 
        tree, branch, relpath = controldir.ControlDir.open_containing_tree_or_branch(
217
 
            directory)
218
 
        public_location = branch.get_public_branch()
219
 
        if public_location:
220
 
            branch = _mod_branch.Branch.open(public_location)
221
 
        if submit_branch is None:
222
 
            submit_branch = branch.get_submit_branch()
223
 
        if submit_branch is None:
224
 
            submit_branch = branch.get_parent()
225
 
        if submit_branch is None:
226
 
            raise errors.BzrCommandError(
227
 
                gettext("No target location specified or remembered"))
228
 
        else:
229
 
            target = _mod_branch.Branch.open(submit_branch)
230
 
        hoster = _mod_propose.get_hoster(branch)
231
 
        for mp in hoster.iter_proposals(branch, target):
232
 
            self.outf.write(gettext('Merge proposal: %s\n') % mp.url)
233
 
 
234
 
 
235
 
class cmd_github_login(Command):
236
 
    __doc__ = """Log into GitHub.
237
 
 
238
 
    When communicating with GitHub, some commands need to authenticate to
239
 
    GitHub.
240
 
    """
241
 
 
242
 
    takes_args = ['username?']
243
 
 
244
 
    def run(self, username=None):
245
 
        from github import Github, GithubException
246
 
        from breezy.config import AuthenticationConfig
247
 
        authconfig = AuthenticationConfig()
248
 
        if username is None:
249
 
            username = authconfig.get_user(
250
 
                'https', 'github.com', prompt=u'GitHub username', ask=True)
251
 
        password = authconfig.get_password('https', 'github.com', username)
252
 
        client = Github(username, password)
253
 
        user = client.get_user()
254
 
        try:
255
 
            authorization = user.create_authorization(
256
 
                scopes=['user', 'repo', 'delete_repo'], note='Breezy',
257
 
                note_url='https://github.com/breezy-team/breezy')
258
 
        except GithubException as e:
259
 
            errs = e.data.get('errors', [])
260
 
            if errs:
261
 
                err_code = errs[0].get('code')
262
 
                if err_code == u'already_exists':
263
 
                    raise errors.BzrCommandError('token already exists')
264
 
            raise errors.BzrCommandError(e.data['message'])
265
 
        # TODO(jelmer): This should really use something in
266
 
        # AuthenticationConfig
267
 
        from .github import store_github_token
268
 
        store_github_token(scheme='https', host='github.com',
269
 
                           token=authorization.token)
270
 
 
271
 
 
272
 
class cmd_gitlab_login(Command):
273
 
    __doc__ = """Log into a GitLab instance.
274
 
 
275
 
    This command takes a GitLab instance URL (e.g. https://gitlab.com)
276
 
    as well as an optional private token. Private tokens can be created via the
277
 
    web UI.
278
 
 
279
 
    :Examples:
280
 
 
281
 
      Log into GNOME's GitLab (prompts for a token):
282
 
 
283
 
         brz gitlab-login https://gitlab.gnome.org/
284
 
 
285
 
      Log into Debian's salsa, using a token created earlier:
286
 
 
287
 
         brz gitlab-login https://salsa.debian.org if4Theis6Eich7aef0zo
288
 
    """
289
 
 
290
 
    takes_args = ['url', 'private_token?']
291
 
 
292
 
    takes_options = [
293
 
        Option('name', help='Name for GitLab site in configuration.',
294
 
               type=str),
295
 
        Option('no-check',
296
 
               "Don't check that the token is valid."),
297
 
        ]
298
 
 
299
 
    def run(self, url, private_token=None, name=None, no_check=False):
300
 
        from breezy import ui
301
 
        from .gitlabs import store_gitlab_token
302
 
        if name is None:
303
 
            try:
304
 
                name = urlutils.parse_url(url)[3].split('.')[-2]
305
 
            except (ValueError, IndexError):
306
 
                raise errors.BzrCommandError(
307
 
                    'please specify a site name with --name')
308
 
        if private_token is None:
309
 
            note("Please visit %s to obtain a private token.",
310
 
                 urlutils.join(url, "profile/personal_access_tokens"))
311
 
            private_token = ui.ui_factory.get_password(u'Private token')
312
 
        if not no_check:
313
 
            from breezy.transport import get_transport
314
 
            from .gitlabs import GitLab
315
 
            GitLab(get_transport(url), private_token=private_token)
316
 
        store_gitlab_token(name=name, url=url, private_token=private_token)
317
 
 
318
 
 
319
 
class cmd_my_merge_proposals(Command):
320
 
    __doc__ = """List all merge proposals owned by the logged-in user.
321
 
 
322
 
    """
323
 
 
324
 
    hidden = True
325
 
 
326
 
    takes_options = [
327
 
        'verbose',
328
 
        RegistryOption.from_kwargs(
329
 
            'status',
330
 
            title='Proposal Status',
331
 
            help='Only include proposals with specified status.',
332
 
            value_switches=True,
333
 
            enum_switch=True,
334
 
            all='All merge proposals',
335
 
            open='Open merge proposals',
336
 
            merged='Merged merge proposals',
337
 
            closed='Closed merge proposals')]
338
 
 
339
 
    def run(self, status='open', verbose=False):
340
 
        for name, hoster_cls in _mod_propose.hosters.items():
341
 
            for instance in hoster_cls.iter_instances():
342
 
                for mp in instance.iter_my_proposals(status=status):
343
 
                    self.outf.write('%s\n' % mp.url)
344
 
                    if verbose:
345
 
                        self.outf.write(
346
 
                            '(Merging %s into %s)\n' %
347
 
                            (mp.get_source_branch_url(),
348
 
                             mp.get_target_branch_url()))
349
 
                        description = mp.get_description()
350
 
                        if description:
351
 
                            self.outf.writelines(
352
 
                                ['\t%s\n' % l
353
 
                                 for l in description.splitlines()])
354
 
                        self.outf.write('\n')
355
 
 
356
 
 
357
 
class cmd_land_merge_proposal(Command):
358
 
    __doc__ = """Land a merge proposal."""
359
 
 
360
 
    takes_args = ['url']
361
 
    takes_options = [
362
 
        Option('message', help='Commit message to use.', type=str)]
363
 
 
364
 
    def run(self, url, message=None):
365
 
        proposal = _mod_propose.get_proposal_by_url(url)
366
 
        proposal.merge(commit_message=message)