/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.13 by Jelmer Vernooij
Add support for labels on merge proposals.
139
    supports_merge_proposal_labels = True
140
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
141
    def __repr__(self):
142
        return "GitHub()"
143
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
144
    def __init__(self):
145
        self.gh = connect_github()
146
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
147
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
148
                        owner=None, revision_id=None, overwrite=False,
149
                        allow_lossy=True):
0.432.12 by Jelmer Vernooij
Fix .git ends.
150
        import github
0.432.3 by Jelmer Vernooij
Publish command works for github.
151
        base_owner, base_project, base_branch_name = parse_github_url(base_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
152
        base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
0.432.3 by Jelmer Vernooij
Publish command works for github.
153
        if owner is None:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
154
            owner = self.gh.get_user().login
0.432.3 by Jelmer Vernooij
Publish command works for github.
155
        if project is None:
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
156
            project = base_repo.name
0.432.3 by Jelmer Vernooij
Publish command works for github.
157
        try:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
158
            remote_repo = self.gh.get_repo('%s/%s' % (owner, project))
0.432.12 by Jelmer Vernooij
Fix .git ends.
159
            remote_repo.id
160
        except github.UnknownObjectException:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
161
            base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
162
            if owner == self.gh.get_user().login:
163
                owner_obj = self.gh.get_user()
0.432.3 by Jelmer Vernooij
Publish command works for github.
164
            else:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
165
                owner_obj = self.gh.get_organization(owner)
0.432.12 by Jelmer Vernooij
Fix .git ends.
166
            remote_repo = owner_obj.create_fork(base_repo)
0.432.3 by Jelmer Vernooij
Publish command works for github.
167
            note(gettext('Forking new repository %s from %s') %
7211.13.7 by Jelmer Vernooij
Fix formatting.
168
                 (remote_repo.html_url, base_repo.html_url))
0.432.3 by Jelmer Vernooij
Publish command works for github.
169
        else:
170
            note(gettext('Reusing existing repository %s') % remote_repo.html_url)
0.431.32 by Jelmer Vernooij
Properly resolve git+ssh URLs.
171
        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.
172
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
173
            push_result = remote_dir.push_branch(
174
                local_branch, revision_id=revision_id, overwrite=overwrite,
175
                name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
176
        except errors.NoRoundtrippingSupport:
177
            if not allow_lossy:
178
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
179
            push_result = remote_dir.push_branch(
180
                local_branch, revision_id=revision_id,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
181
                overwrite=overwrite, name=name, lossy=True)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
182
        return push_result.target_branch, github_url_to_bzr_url(
7211.13.7 by Jelmer Vernooij
Fix formatting.
183
            remote_repo.html_url, name)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
184
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
185
    def get_push_url(self, branch):
186
        owner, project, branch_name = parse_github_url(branch)
187
        repo = self.gh.get_repo('%s/%s' % (owner, project))
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
188
        return github_url_to_bzr_url(repo.ssh_url, branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
189
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
190
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
191
        import github
192
        base_owner, base_project, base_branch_name = parse_github_url(base_branch)
193
        base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
194
        if owner is None:
195
            owner = self.gh.get_user().login
196
        if project is None:
197
            project = base_repo.name
198
        try:
199
            remote_repo = self.gh.get_repo('%s/%s' % (owner, project))
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
200
            full_url = github_url_to_bzr_url(remote_repo.ssh_url, name)
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
201
            return _mod_branch.Branch.open(full_url)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
202
        except github.UnknownObjectException:
203
            raise errors.NotBranchError('https://github.com/%s/%s' % (owner, project))
204
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
205
    def get_proposer(self, source_branch, target_branch):
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
206
        return GitHubMergeProposalBuilder(self.gh, source_branch, target_branch)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
207
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
208
    def get_proposal(self, source_branch, target_branch):
209
        (source_owner, source_repo_name, source_branch_name) = (
7211.13.7 by Jelmer Vernooij
Fix formatting.
210
            parse_github_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
211
        (target_owner, target_repo_name, target_branch_name) = (
7211.13.7 by Jelmer Vernooij
Fix formatting.
212
            parse_github_url(target_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
213
        target_repo = self.gh.get_repo("%s/%s" % (target_owner, target_repo_name))
214
        for pull in target_repo.get_pulls(head=target_branch_name):
215
            if pull.head.ref != source_branch_name:
216
                continue
217
            if (pull.head.repo.owner.login != source_owner or
7211.13.7 by Jelmer Vernooij
Fix formatting.
218
                    pull.head.repo.name != source_repo_name):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
219
                continue
0.431.44 by Jelmer Vernooij
Support get/set description.
220
            return GitHubMergeProposal(pull)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
221
        raise NoMergeProposal()
222
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
223
    def hosts(self, branch):
224
        try:
225
            parse_github_url(branch)
226
        except NotGitHubUrl:
227
            return False
228
        else:
229
            return True
230
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
231
    @classmethod
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
232
    def probe(cls, branch):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
233
        try:
234
            parse_github_url(branch)
235
        except NotGitHubUrl:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
236
            raise UnsupportedHoster(branch)
237
        return cls()
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
238
239
0.432.2 by Jelmer Vernooij
Publish command sort of works.
240
class GitHubMergeProposalBuilder(MergeProposalBuilder):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
241
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
242
    def __init__(self, gh, source_branch, target_branch):
243
        self.gh = gh
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
244
        self.source_branch = source_branch
245
        self.target_branch = target_branch
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
246
        (self.target_owner, self.target_repo_name, self.target_branch_name) = (
7211.13.7 by Jelmer Vernooij
Fix formatting.
247
            parse_github_url(self.target_branch))
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
248
        (self.source_owner, self.source_repo_name, self.source_branch_name) = (
7211.13.7 by Jelmer Vernooij
Fix formatting.
249
            parse_github_url(self.source_branch))
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
250
251
    def get_infotext(self):
252
        """Determine the initial comment for the merge proposal."""
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
253
        info = []
254
        info.append("Merge %s into %s:%s\n" % (
255
            self.source_branch_name, self.target_owner,
256
            self.target_branch_name))
257
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
258
        info.append("Target: %s\n" % self.target_branch.user_url)
259
        return ''.join(info)
260
261
    def get_initial_body(self):
262
        """Get a body for the proposal for the user to modify.
263
264
        :return: a str or None.
265
        """
266
        return None
267
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
268
    def create_proposal(self, description, reviewers=None, labels=None,
269
                        prerequisite_branch=None):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
270
        """Perform the submission."""
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
271
        if prerequisite_branch is not None:
272
            raise PrerequisiteBranchUnsupported(self)
0.432.10 by Jelmer Vernooij
More test fixes.
273
        import github
0.432.7 by Jelmer Vernooij
propose works \o/
274
        # TODO(jelmer): Probe for right repo name
0.432.12 by Jelmer Vernooij
Fix .git ends.
275
        if self.target_repo_name.endswith('.git'):
276
            self.target_repo_name = self.target_repo_name[:-4]
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
277
        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.
278
        # TODO(jelmer): Allow setting title explicitly?
0.431.44 by Jelmer Vernooij
Support get/set description.
279
        title = determine_title(description)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
280
        # TOOD(jelmer): Set maintainers_can_modify?
0.432.10 by Jelmer Vernooij
More test fixes.
281
        try:
282
            pull_request = target_repo.create_pull(
283
                title=title, body=description,
284
                head="%s:%s" % (self.source_owner, self.source_branch_name),
285
                base=self.target_branch_name)
286
        except github.GithubException as e:
287
            if e.status == 422:
288
                raise MergeProposalExists(self.source_branch.user_url)
289
            raise
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
290
        if reviewers:
291
            for reviewer in reviewers:
292
                pull_request.assignees.append(
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
293
                    self.gh.get_user(reviewer))
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
294
        if labels:
295
            for label in labels:
296
                pull_request.issue.labels.append(label)
0.431.44 by Jelmer Vernooij
Support get/set description.
297
        return GitHubMergeProposal(pull_request)