/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: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

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