-
-
Notifications
You must be signed in to change notification settings - Fork 6
/
github_read_write.py
155 lines (144 loc) · 5.45 KB
/
github_read_write.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
"""
This class knows how to read and write LARGE files to Github. The regular
GitHub Contents API can't handle files larger than 1MB - this class knows how
to spot that proble and switch to the large-file-supporting low level Git Data
API instead.
https://developer.github.com/v3/repos/contents/
https://developer.github.com/v3/git/
"""
import requests
class GithubContent(object):
class NotFound(Exception):
pass
class UnknownError(Exception):
pass
def __init__(self, owner, repo, token):
self.owner = owner
self.repo = repo
self.token = token
def base_url(self):
return 'https://api.github.com/repos/%s/%s' % (
self.owner, self.repo
)
def read(self, filepath):
# Try reading using content API
content_url = self.base_url() + '/contents/%s' % filepath
response = requests.get(
content_url,
headers={
'Authorization': 'token %s' % self.token
}
)
if response.status_code == 200:
data = response.json()
return data['content'].decode('base64'), data['sha']
elif response.status_code == 404:
raise self.NotFound(filepath)
elif response.status_code == 403:
# It's probably too large
if response.json()['errors'][0]['code'] != 'too_large':
raise self.UnknownError(response.content)
else:
return self.read_large(filepath)
else:
raise self.UnknownError(response.content)
def read_large(self, filepath):
master = requests.get(
self.base_url() + '/git/trees/master?recursive=1',
headers={
'Authorization': 'token %s' % self.token
}
).json()
try:
tree_entry = [t for t in master['tree'] if t['path'] == filepath][0]
except IndexError:
raise self.NotFound(filepath)
data = requests.get(
tree_entry['url'],
headers={
'Authorization': 'token %s' % self.token
}
).json()
return data['content'].decode('base64'), data['sha']
def write(self, filepath, content, sha=None, commit_message=None, committer=None):
github_url = self.base_url() + '/contents/%s' % filepath
payload = {
'path': filepath,
'content': content.encode('base64'),
'message': commit_message,
}
if sha:
payload['sha'] = sha
if committer:
payload['committer'] = committer
response = requests.put(
github_url,
json=payload,
headers={
'Authorization': 'token %s' % self.token
}
)
if response.status_code == 403 and response.json()['errors'][0]['code'] == 'too_large':
return self.write_large(filepath, content, commit_message, committer)
elif sha is None and response.status_code == 422 and 'sha' in response.json().get('message', ''):
# Missing sha - we need to figure out the sha and try again
old_content, old_sha = self.read(filepath)
return self.write(
filepath,
content,
sha=old_sha,
commit_message=commit_message,
committer=committer,
)
elif response.status_code in (201, 200):
updated = response.json()
return updated['content']['sha'], updated['commit']['sha']
else:
raise self.UnknownError(str(response.status_code) + ':' + response.content)
def write_large(self, filepath, content, commit_message=None, committer=None):
# Create a new blob with the file contents
created_blob = requests.post(self.base_url() + '/git/blobs', json={
'encoding': 'utf8',
'content': content,
}, headers={'Authorization': 'token %s' % self.token}).json()
# Retrieve master tree sha
master_sha = requests.get(
self.base_url() + '/git/trees/master?recursive=1',
headers={
'Authorization': 'token %s' % self.token
}
).json()['sha']
# Construct a new tree
created_tree = requests.post(
self.base_url() + '/git/trees',
json={
'base_tree': master_sha,
'tree': [{
'mode': '100644', # file (blob),
'path': filepath,
'type': 'blob',
'sha': created_blob['sha'],
}]
},
headers={'Authorization': 'token %s' % self.token}
).json()
# Create a commit which references the new tree
payload = {
'message': commit_message,
'parents': [master_sha],
'tree': created_tree['sha'],
}
if committer:
payload['committer'] = committer
created_commit = requests.post(
self.base_url() + '/git/commits',
json=payload,
headers={'Authorization': 'token %s' % self.token}
).json()
# Move HEAD reference on master to the new commit
requests.patch(
self.base_url() + '/git/refs/heads/master',
json={'sha': created_commit['sha']},
headers={'Authorization': 'token %s' % self.token}
).json()
return created_blob['sha'], created_commit['sha']