forked from TransformerOptimus/SuperAGI
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgithub_helper.py
319 lines (270 loc) · 12.6 KB
/
github_helper.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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
import base64
import re
import requests
from superagi.lib.logger import logger
class GithubHelper:
def __init__(self, github_access_token, github_username):
"""
Initializes the GithubHelper with the provided access token and username.
Args:
github_access_token (str): Personal GitHub access token.
github_username (str): GitHub username.
"""
self.github_access_token = github_access_token
self.github_username = github_username
def get_file_path(self, file_name, folder_path):
"""
Returns the path of the given file with respect to the specified folder.
Args:
file_name (str): Name of the file.
folder_path (str): Path to the folder.
Returns:
str: Combined file path.
"""
file_path = f'{folder_path}'
if folder_path:
file_path += '/'
file_path += file_name
return file_path
def check_repository_visibility(self, repository_owner, repository_name):
"""
Checks the visibility (public/private) of a given repository.
Args:
repository_owner (str): Owner of the repository.
repository_name (str): Name of the repository.
Returns:
bool: True if the repository is private, False if it's public.
"""
url = f"https://api.github.com/repos/{repository_owner}/{repository_name}"
headers = {
"Authorization": f"Token {self.github_access_token}",
"Accept": "application/vnd.github.v3+json"
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
repository_data = response.json()
return repository_data['private']
else:
logger.info(f"Failed to fetch repository information: {response.status_code} - {response.text}")
return None
def search_repo(self, repository_owner, repository_name, file_name, folder_path=None):
"""
Searches for a file in the given repository and returns the file's metadata.
Args:
repository_owner (str): Owner of the repository.
repository_name (str): Name of the repository.
file_name (str): Name of the file to search for.
folder_path (str, optional): Path to the folder containing the file. Defaults to None.
Returns:
dict: File metadata.
"""
headers = {
"Authorization": f"token {self.github_access_token}" if self.github_access_token else None,
"Content-Type": "application/vnd.github+json"
}
file_path = self.get_file_path(file_name, folder_path)
url = f'https://api.github.com/repos/{repository_owner}/{repository_name}/contents/{file_path}'
r = requests.get(url, headers=headers)
r.raise_for_status()
data = r.json()
return data
def sync_branch(self, repository_owner, repository_name, base_branch, head_branch, headers):
"""
Syncs the head branch with the base branch.
Args:
repository_owner (str): Owner of the repository.
repository_name (str): Name of the repository.
base_branch (str): Base branch to sync with.
head_branch (str): Head branch to sync.
headers (dict): Request headers.
Returns:
None
"""
base_branch_url = f'https://api.github.com/repos/{repository_owner}/{repository_name}/branches/{base_branch}'
response = requests.get(base_branch_url, headers=headers)
response_json = response.json()
base_commit_sha = response_json['commit']['sha']
head_branch_url = f'https://api.github.com/repos/{self.github_username}/{repository_name}/git/refs/heads/{head_branch}'
data = {
'sha': base_commit_sha,
'force': True
}
response = requests.patch(head_branch_url, json=data, headers=headers)
if response.status_code == 200:
logger.info(
f'Successfully synced {self.github_username}:{head_branch} branch with {repository_owner}:{base_branch}')
else:
logger.info('Failed to sync the branch. Check your inputs and permissions.')
def make_fork(self, repository_owner, repository_name, base_branch, headers):
"""
Creates a fork of the given repository.
Args:
repository_owner (str): Owner of the repository.
repository_name (str): Name of the repository.
base_branch (str): Base branch to sync with.
headers (dict): Request headers.
Returns:
int: Status code of the fork request.
"""
fork_url = f'https://api.github.com/repos/{repository_owner}/{repository_name}/forks'
fork_response = requests.post(fork_url, headers=headers)
if fork_response.status_code == 202:
logger.info('Fork created successfully.')
self.sync_branch(repository_owner, repository_name, base_branch, base_branch, headers)
else:
logger.info('Failed to create the fork:', fork_response.json()['message'])
return fork_response.status_code
def create_branch(self, repository_name, base_branch, head_branch, headers):
"""
Creates a new branch in the given repository.
Args:
repository_name (str): Name of the repository.
base_branch (str): Base branch to sync with.
head_branch (str): Head branch to sync.
headers (dict): Request headers.
Returns:
int: Status code of the branch creation request.
"""
branch_url = f'https://api.github.com/repos/{self.github_username}/{repository_name}/git/refs'
branch_params = {
'ref': f'refs/heads/{head_branch}',
'sha': requests.get(
f'https://api.github.com/repos/{self.github_username}/{repository_name}/git/refs/heads/{base_branch}',
headers=headers).json()['object']['sha']
}
branch_response = requests.post(branch_url, json=branch_params, headers=headers)
if branch_response.status_code == 201:
logger.info('Branch created successfully.')
elif branch_response.status_code == 422:
logger.info('Branch new-file already exists, making commits to new-file branch')
else:
logger.info('Failed to create branch:', branch_response.json()['message'])
return branch_response.status_code
def delete_file(self, repository_name, file_name, folder_path, commit_message, head_branch, headers):
"""
Deletes a file or folder from the given repository.
Args:
repository_name (str): Name of the repository.
file_name (str): Name of the file to delete.
folder_path (str): Path to the folder containing the file.
commit_message (str): Commit message.
head_branch (str): Head branch to sync.
headers (dict): Request headers.
Returns:
int: Status code of the file deletion request.
"""
file_path = self.get_file_path(file_name, folder_path)
file_url = f'https://api.github.com/repos/{self.github_username}/{repository_name}/contents/{file_path}'
file_params = {
'message': commit_message,
'sha': self.get_sha(self.github_username, repository_name, file_name, folder_path),
'branch': head_branch
}
file_response = requests.delete(file_url, json=file_params, headers=headers)
if file_response.status_code == 200:
logger.info('File or folder delete successfully.')
else:
logger.info('Failed to Delete file or folder:', file_response.json())
return file_response.status_code
def add_file(self, repository_owner, repository_name, file_name, folder_path, head_branch, base_branch, headers,
body, commit_message):
"""
Adds a file to the given repository.
Args:
repository_owner (str): Owner of the repository.
repository_name (str): Name of the repository.
file_name (str): Name of the file to add.
folder_path (str): Path to the folder containing the file.
head_branch (str): Head branch to sync.
base_branch (str): Base branch to sync with.
Returns:
None
"""
body_bytes = body.encode("ascii")
base64_bytes = base64.b64encode(body_bytes)
file_content = base64_bytes.decode("ascii")
file_path = self.get_file_path(file_name, folder_path)
file_url = f'https://api.github.com/repos/{self.github_username}/{repository_name}/contents/{file_path}'
file_params = {
'message': commit_message,
'content': file_content,
'branch': head_branch
}
file_response = requests.put(file_url, json=file_params, headers=headers)
if file_response.status_code == 201:
logger.info('File content uploaded successfully.')
elif file_response.status_code == 422:
logger.info('File already exists')
else:
logger.info('Failed to upload file content:', file_response.json()['message'])
return file_response.status_code
def create_pull_request(self, repository_owner, repository_name, head_branch, base_branch, headers):
"""
Creates a pull request in the given repository.
Args:
repository_owner (str): Owner of the repository.
repository_name (str): Name of the repository.
head_branch (str): Head branch to sync.
base_branch (str): Base branch to sync with.
headers (dict): Request headers.
Returns:
int: Status code of the pull request creation request.
"""
pull_request_url = f'https://api.github.com/repos/{repository_owner}/{repository_name}/pulls'
pull_request_params = {
'title': f'Pull request by {self.github_username}',
'body': 'Please review and merge this change.',
'head': f'{self.github_username}:{head_branch}', # required for cross repository only
'head_repo': repository_name, # required for cross repository only
'base': base_branch
}
pr_response = requests.post(pull_request_url, json=pull_request_params, headers=headers)
if pr_response.status_code == 201:
logger.info('Pull request created successfully.')
elif pr_response.status_code == 422:
logger.info('Added changes to already existing pull request')
else:
logger.info('Failed to create pull request:', pr_response.json()['message'])
return pr_response.status_code
def get_sha(self, repository_owner, repository_name, file_name, folder_path=None):
"""
Gets the sha of the file to be deleted.
Args:
repository_owner (str): Owner of the repository.
repository_name (str): Name of the repository.
file_name (str): Name of the file to delete.
folder_path (str): Path to the folder containing the file.
Returns:
str: Sha of the file to be deleted.
"""
data = self.search_repo(repository_owner, repository_name, file_name, folder_path)
return data['sha']
def get_content_in_file(self, repository_owner, repository_name, file_name, folder_path=None):
"""
Gets the content of the file.
Args:
repository_owner (str): Owner of the repository.
repository_name (str): Name of the repository.
file_name (str): Name of the file to delete.
folder_path (str): Path to the folder containing the file.
Returns:
str: Content of the file.
"""
data = self.search_repo(repository_owner, repository_name, file_name, folder_path)
file_content = data['content']
file_content_encoding = data.get('encoding')
if file_content_encoding == 'base64':
file_content = base64.b64decode(file_content).decode()
return file_content
@classmethod
def validate_github_link(cls, link: str) -> bool:
"""
Validate a GitHub link.
Returns True if the link is valid, False otherwise.
"""
# Regular expression pattern to match a GitHub link
pattern = r'^https?://(?:www\.)?github\.com/[\w\-]+/[\w\-]+$'
# Check if the link matches the pattern
if re.match(pattern, link):
return True
return False