/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
1
# Copyright (C) 2018 Breezy Developers
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
0.434.1 by Jelmer Vernooij
Use absolute_import.
17
"""Support for GitHub."""
18
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
19
from __future__ import absolute_import
20
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
21
import os
22
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
23
from .propose import (
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
24
    Hoster,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
25
    MergeProposal,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
26
    MergeProposalBuilder,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
27
    MergeProposalExists,
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
28
    NoMergeProposal,
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
29
    PrerequisiteBranchUnsupported,
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
30
    UnsupportedHoster,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
31
    )
32
33
from ... import (
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
34
    branch as _mod_branch,
0.432.3 by Jelmer Vernooij
Publish command works for github.
35
    controldir,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
36
    errors,
37
    hooks,
38
    urlutils,
0.432.3 by Jelmer Vernooij
Publish command works for github.
39
    version_string as breezy_version,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
40
    )
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
41
from ...config import AuthenticationConfig, GlobalStack, config_dir
0.431.32 by Jelmer Vernooij
Properly resolve git+ssh URLs.
42
from ...git.urls import git_url_to_bzr_url
0.432.3 by Jelmer Vernooij
Publish command works for github.
43
from ...i18n import gettext
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
44
from ...sixish import PY3
0.432.3 by Jelmer Vernooij
Publish command works for github.
45
from ...trace import note
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
46
from ...lazy_import import lazy_import
47
lazy_import(globals(), """
48
from github import Github
49
""")
50
51
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
52
def store_github_token(scheme, host, token):
53
    with open(os.path.join(config_dir(), 'github.conf'), 'w') as f:
54
        f.write(token)
55
56
57
def retrieve_github_token(scheme, host):
58
    path = os.path.join(config_dir(), 'github.conf')
59
    if not os.path.exists(path):
60
        return None
0.435.1 by Jelmer Vernooij
Fix reading github credentials.
61
    with open(path, 'r') as f:
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
62
        return f.read().strip()
63
64
0.431.44 by Jelmer Vernooij
Support get/set description.
65
def determine_title(description):
66
    return description.splitlines()[0]
67
68
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
69
class NotGitHubUrl(errors.BzrError):
70
71
    _fmt = "Not a GitHub URL: %(url)s"
72
73
    def __init__(self, url):
74
        errors.BzrError.__init__(self)
75
        self.url = url
76
77
78
def connect_github():
7211.13.7 by Jelmer Vernooij
Fix formatting.
79
    """Connect to GitHub.
80
    """
81
    user_agent = "Breezy/%s" % breezy_version
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
82
83
    auth = AuthenticationConfig()
84
85
    credentials = auth.get_credentials('https', 'github.com')
86
    if credentials is not None:
0.432.3 by Jelmer Vernooij
Publish command works for github.
87
        return Github(credentials['user'], credentials['password'],
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
88
                      user_agent=user_agent)
89
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
90
    # TODO(jelmer): token = auth.get_token('https', 'github.com')
91
    token = retrieve_github_token('https', 'github.com')
92
    if token is not None:
0.431.61 by Jelmer Vernooij
Fix token login.
93
        return Github(token, user_agent=user_agent)
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
94
    else:
95
        note('Accessing GitHub anonymously. To log in, run \'brz gh-login\'.')
96
        return Github(user_agent=user_agent)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
97
98
0.431.44 by Jelmer Vernooij
Support get/set description.
99
class GitHubMergeProposal(MergeProposal):
100
101
    def __init__(self, pr):
102
        self._pr = pr
103
104
    @property
105
    def url(self):
106
        return self._pr.html_url
107
108
    def get_description(self):
109
        return self._pr.body
110
111
    def set_description(self, description):
112
        self._pr.edit(body=description, title=determine_title(description))
113
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
114
    def is_merged(self):
115
        return self._pr.merged
116
0.431.44 by Jelmer Vernooij
Support get/set description.
117
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
118
def parse_github_url(branch):
119
    url = urlutils.split_segment_parameters(branch.user_url)[0]
120
    (scheme, user, password, host, port, path) = urlutils.parse_url(
121
        url)
122
    if host != 'github.com':
123
        raise NotGitHubUrl(url)
124
    (owner, repo_name) = path.strip('/').split('/')
0.432.12 by Jelmer Vernooij
Fix .git ends.
125
    if repo_name.endswith('.git'):
126
        repo_name = repo_name[:-4]
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
127
    return owner, repo_name, branch.name
128
129
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
130
def github_url_to_bzr_url(url, branch_name):
131
    if not PY3:
132
        branch_name = branch_name.encode('utf-8')
133
    return urlutils.join_segment_parameters(
7211.13.7 by Jelmer Vernooij
Fix formatting.
134
        git_url_to_bzr_url(url), {"branch": branch_name})
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
135
136
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
137
class GitHub(Hoster):
138
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
139
    name = 'github'
140
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
141
    supports_merge_proposal_labels = True
142
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
143
    def __repr__(self):
144
        return "GitHub()"
145
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
146
    def __init__(self):
147
        self.gh = connect_github()
148
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
149
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
150
                        owner=None, revision_id=None, overwrite=False,
151
                        allow_lossy=True):
0.432.12 by Jelmer Vernooij
Fix .git ends.
152
        import github
0.432.3 by Jelmer Vernooij
Publish command works for github.
153
        base_owner, base_project, base_branch_name = parse_github_url(base_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
154
        base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
0.432.3 by Jelmer Vernooij
Publish command works for github.
155
        if owner is None:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
156
            owner = self.gh.get_user().login
0.432.3 by Jelmer Vernooij
Publish command works for github.
157
        if project is None:
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
158
            project = base_repo.name
0.432.3 by Jelmer Vernooij
Publish command works for github.
159
        try:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
160
            remote_repo = self.gh.get_repo('%s/%s' % (owner, project))
0.432.12 by Jelmer Vernooij
Fix .git ends.
161
            remote_repo.id
162
        except github.UnknownObjectException:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
163
            base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
164
            if owner == self.gh.get_user().login:
165
                owner_obj = self.gh.get_user()
0.432.3 by Jelmer Vernooij
Publish command works for github.
166
            else:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
167
                owner_obj = self.gh.get_organization(owner)
0.432.12 by Jelmer Vernooij
Fix .git ends.
168
            remote_repo = owner_obj.create_fork(base_repo)
0.432.3 by Jelmer Vernooij
Publish command works for github.
169
            note(gettext('Forking new repository %s from %s') %
7211.13.7 by Jelmer Vernooij
Fix formatting.
170
                 (remote_repo.html_url, base_repo.html_url))
0.432.3 by Jelmer Vernooij
Publish command works for github.
171
        else:
172
            note(gettext('Reusing existing repository %s') % remote_repo.html_url)
0.431.32 by Jelmer Vernooij
Properly resolve git+ssh URLs.
173
        remote_dir = controldir.ControlDir.open(git_url_to_bzr_url(remote_repo.ssh_url))
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
174
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
175
            push_result = remote_dir.push_branch(
176
                local_branch, revision_id=revision_id, overwrite=overwrite,
177
                name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
178
        except errors.NoRoundtrippingSupport:
179
            if not allow_lossy:
180
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
181
            push_result = remote_dir.push_branch(
182
                local_branch, revision_id=revision_id,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
183
                overwrite=overwrite, name=name, lossy=True)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
184
        return push_result.target_branch, github_url_to_bzr_url(
7211.13.7 by Jelmer Vernooij
Fix formatting.
185
            remote_repo.html_url, name)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
186
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
187
    def get_push_url(self, branch):
188
        owner, project, branch_name = parse_github_url(branch)
189
        repo = self.gh.get_repo('%s/%s' % (owner, project))
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
190
        return github_url_to_bzr_url(repo.ssh_url, branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
191
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
192
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
193
        import github
194
        base_owner, base_project, base_branch_name = parse_github_url(base_branch)
195
        base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
196
        if owner is None:
197
            owner = self.gh.get_user().login
198
        if project is None:
199
            project = base_repo.name
200
        try:
201
            remote_repo = self.gh.get_repo('%s/%s' % (owner, project))
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
202
            full_url = github_url_to_bzr_url(remote_repo.ssh_url, name)
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
203
            return _mod_branch.Branch.open(full_url)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
204
        except github.UnknownObjectException:
205
            raise errors.NotBranchError('https://github.com/%s/%s' % (owner, project))
206
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
207
    def get_proposer(self, source_branch, target_branch):
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
208
        return GitHubMergeProposalBuilder(self.gh, source_branch, target_branch)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
209
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
210
    def get_proposal(self, source_branch, target_branch):
211
        (source_owner, source_repo_name, source_branch_name) = (
7211.13.7 by Jelmer Vernooij
Fix formatting.
212
            parse_github_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
213
        (target_owner, target_repo_name, target_branch_name) = (
7211.13.7 by Jelmer Vernooij
Fix formatting.
214
            parse_github_url(target_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
215
        target_repo = self.gh.get_repo("%s/%s" % (target_owner, target_repo_name))
216
        for pull in target_repo.get_pulls(head=target_branch_name):
217
            if pull.head.ref != source_branch_name:
218
                continue
219
            if (pull.head.repo.owner.login != source_owner or
7211.13.7 by Jelmer Vernooij
Fix formatting.
220
                    pull.head.repo.name != source_repo_name):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
221
                continue
0.431.44 by Jelmer Vernooij
Support get/set description.
222
            return GitHubMergeProposal(pull)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
223
        raise NoMergeProposal()
224
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
225
    def hosts(self, branch):
226
        try:
227
            parse_github_url(branch)
228
        except NotGitHubUrl:
229
            return False
230
        else:
231
            return True
232
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
233
    @classmethod
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
234
    def probe(cls, branch):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
235
        try:
236
            parse_github_url(branch)
237
        except NotGitHubUrl:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
238
            raise UnsupportedHoster(branch)
239
        return cls()
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
240
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
241
    @classmethod
242
    def iter_instances(cls):
243
        yield cls()
244
245
    def iter_my_proposals(self):
246
        for issue in self.gh.search_issues(
247
                query='is:pr is:open author:%s' % self.gh.get_user().login):
248
            yield GitHubMergeProposal(issue.pull_request)
249
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
250
0.432.2 by Jelmer Vernooij
Publish command sort of works.
251
class GitHubMergeProposalBuilder(MergeProposalBuilder):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
252
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
253
    def __init__(self, gh, source_branch, target_branch):
254
        self.gh = gh
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
255
        self.source_branch = source_branch
256
        self.target_branch = target_branch
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
257
        (self.target_owner, self.target_repo_name, self.target_branch_name) = (
7211.13.7 by Jelmer Vernooij
Fix formatting.
258
            parse_github_url(self.target_branch))
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
259
        (self.source_owner, self.source_repo_name, self.source_branch_name) = (
7211.13.7 by Jelmer Vernooij
Fix formatting.
260
            parse_github_url(self.source_branch))
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
261
262
    def get_infotext(self):
263
        """Determine the initial comment for the merge proposal."""
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
264
        info = []
265
        info.append("Merge %s into %s:%s\n" % (
266
            self.source_branch_name, self.target_owner,
267
            self.target_branch_name))
268
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
269
        info.append("Target: %s\n" % self.target_branch.user_url)
270
        return ''.join(info)
271
272
    def get_initial_body(self):
273
        """Get a body for the proposal for the user to modify.
274
275
        :return: a str or None.
276
        """
277
        return None
278
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
279
    def create_proposal(self, description, reviewers=None, labels=None,
280
                        prerequisite_branch=None):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
281
        """Perform the submission."""
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
282
        if prerequisite_branch is not None:
283
            raise PrerequisiteBranchUnsupported(self)
0.432.10 by Jelmer Vernooij
More test fixes.
284
        import github
0.432.7 by Jelmer Vernooij
propose works \o/
285
        # TODO(jelmer): Probe for right repo name
0.432.12 by Jelmer Vernooij
Fix .git ends.
286
        if self.target_repo_name.endswith('.git'):
287
            self.target_repo_name = self.target_repo_name[:-4]
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
288
        target_repo = self.gh.get_repo("%s/%s" % (self.target_owner, self.target_repo_name))
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
289
        # TODO(jelmer): Allow setting title explicitly?
0.431.44 by Jelmer Vernooij
Support get/set description.
290
        title = determine_title(description)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
291
        # TOOD(jelmer): Set maintainers_can_modify?
0.432.10 by Jelmer Vernooij
More test fixes.
292
        try:
293
            pull_request = target_repo.create_pull(
294
                title=title, body=description,
295
                head="%s:%s" % (self.source_owner, self.source_branch_name),
296
                base=self.target_branch_name)
297
        except github.GithubException as e:
298
            if e.status == 422:
299
                raise MergeProposalExists(self.source_branch.user_url)
300
            raise
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
301
        if reviewers:
302
            for reviewer in reviewers:
303
                pull_request.assignees.append(
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
304
                    self.gh.get_user(reviewer))
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
305
        if labels:
306
            for label in labels:
307
                pull_request.issue.labels.append(label)
0.431.44 by Jelmer Vernooij
Support get/set description.
308
        return GitHubMergeProposal(pull_request)