forked from readthedocs/readthedocs.org
-
Notifications
You must be signed in to change notification settings - Fork 0
/
github.py
180 lines (155 loc) · 6.21 KB
/
github.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import logging
from django.conf import settings
from github2.client import Github
from vcs_support.base import BaseContributionBackend
import base64
import os
import urllib
import urllib2
log = logging.getLogger(__name__)
GITHUB_URLS = ('git://github.com', 'https://github.com', 'http://github.com')
GITHUB_TOKEN = getattr(settings, 'GITHUB_TOKEN', None)
GITHUB_USERNAME = getattr(settings, 'GITHUB_USERNAME', None)
GITHUB_OKAY = GITHUB_TOKEN and GITHUB_USERNAME
class GithubContributionBackend(BaseContributionBackend):
fallback_branch = 'master'
def __init__(self, *args, **kwargs):
super(GithubContributionBackend, self).__init__(*args, **kwargs)
self.gh = Github(username=GITHUB_USERNAME, api_token=GITHUB_TOKEN)
def run(self, *args):
log.debug(args)
ret = super(GithubContributionBackend, self).run(*args)
log.debug(ret)
return ret
@classmethod
def accepts(cls, url):
return url.startswith(GITHUB_URLS) and GITHUB_OKAY
def get_branch_identifier(self, branch):
identifier = 'rtd-%s-%s' % (branch.user.username, branch.pk)
if self._branch_exists(identifier):
return identifier
self.create_branch(identifier)
return identifier
def branch_exists(self, identifier):
return identifier in self.run('git', 'branch')[1]
def create_branch(self, identifier):
self.repo.update()
branch = self.fallback_branch
if self.default_branch:
branch = self.default_branch
self.run('git', 'branch', '--track', identifier, branch)
def get_branch_file(self, branch, filename):
"""
git show branch:file
"""
identifier = self.get_branch_identifier(branch)
return self.run('git', 'show', '%s:%s' % (identifier, filename))[1]
def set_branch_file(self, branch, filename, contents, comment=''):
"""
git checkout branch
git commit --author "Name Surname <email@address.com>" -m comment
git checkout master
"""
identifier = self.get_branch_identifier(branch)
self.run('git', 'checkout', identifier)
with self._open_file(filename, 'wb') as fobj:
fobj.write(contents)
self.run('git', 'add', filename)
if not comment:
comment = 'no comment'
name, email = branch.user.get_profile().get_contribution_details()
author = u"%s <%s>" % (name, email)
self.run('git', 'commit', '-m', comment, '--author', author)
branch = self.fallback_branch
if self.default_branch:
branch = self.default_branch
self.run('git', 'checkout', branch)
def push_branch(self, branch, title='', comment=''):
"""
Pushes a branch upstream.
Since the python github API libraries don't support pull requests, we'll
have to do it manually using urllib2 :(
"""
identifier = self.get_branch_identifier(branch)
log.info('pushing branch %s in %s' % (identifier, self._gh_name()))
# first push the branch to the rtd-account on github.
self.check_remote()
self.push_remote(identifier)
# now make the pull request.
if not title:
title = 'Documentation changes from readthedocs.org'
if not comment:
comment = 'These changes have been done on readthedocs.org'
self.pull_request(identifier, title, comment)
self.run('git', 'branch', '-D', identifier)
def pull_request(self, identifier, title, comment):
"""
Open an actual pull request
"""
log.info('pull request %s:%s to %s (%s/%s)' % (
GITHUB_USERNAME, identifier, self.gh_name(), title, comment))
url = 'https://github.com/api/v2/json/pulls/%s' % self._gh_name()
log.debug(url)
request = urllib2.Request(url)
auth = base64.encodestring('%s/token:%s' % (
GITHUB_USERNAME, GITHUB_TOKEN))[:-1]
request.add_header("Authorization", 'Basic %s' % auth)
branch = self.fallback_branch
if self.default_branch:
branch = self.default_branch
data = {
'base': branch,
'head': '%s:%s' % (GITHUB_USERNAME, identifier),
'title': title,
'body': comment,
}
pull_request_data = [("pull[%s]" % k, v) for k, v in data.items()]
postdata = urllib.urlencode(pull_request_data)
log.debug('postdata: %s' % postdata)
handler = urllib2.urlopen(request, postdata)
log.debug(handler.headers.dict)
log.debug(handler.read())
def gh_name(self):
return '%s/%s' % (self.gh_user(), self.gh_reponame())
def gh_user(self):
return self.repo_url.split('/')[-2]
def gh_reponame(self):
name = self.repo_url.split('/')[-1]
if name.endswith('.git'):
return name[:-4]
return name
def check_remote(self):
"""
Check if the RTD remote is available in this repository, if not, add it.
"""
log.info('checking remote')
if not self.has_fork():
log.info('no fork available')
self.fork()
else:
log.info('fork found')
if 'rtd' not in self.run('git', 'remote')[1]:
log.info('rtd remote not yet specified')
self.run('git', 'remote', 'add', 'rtd', self.get_remote_name())
else:
log.info('rtd remote found')
def get_remote_name(self):
return 'git@github.com:%s/%s.git' % (
GITHUB_USERNAME, self.gh_reponame())
def push_remote(self, identifier):
"""
push a local branch to the RTD remote
"""
log.info('pushing %s to remote %s' % (identifier, self.get_remote_name()))
self.run('git', 'push', 'rtd', identifier)
def has_fork(self):
return self.gh_reponame() in [
r.name for r in self.gh.repos.list(GITHUB_USERNAME)]
def fork(self):
log.info('forking %s to %s' % (self.gh_name(), GITHUB_USERNAME))
log.info(self.gh.repos.fork(self.gh_name()))
@property
def env(self):
env = super(GithubContributionBackend, self).env
env['GIT_DIR'] = os.path.join(self.working_dir, '.git')
return env