-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathcontribute.py
More file actions
198 lines (172 loc) · 7.5 KB
/
contribute.py
File metadata and controls
198 lines (172 loc) · 7.5 KB
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
import github
from github import Github
import os,sys,platform,base64,time,re
# Initializing the Variables
BOT_TOKEN = os.environ.get('CONCORE_BOT_TOKEN', '')
# Fail fast if token is missing
if not BOT_TOKEN:
print("Error: CONCORE_BOT_TOKEN environment variable is not set.")
sys.exit(1)
# Token format validation
token_pattern = r"^((ghp_|github_pat_|ghs_)[A-Za-z0-9_]{20,}|[0-9a-fA-F]{40})$"
if not re.match(token_pattern, BOT_TOKEN):
print("Error: Invalid GitHub token format.")
sys.exit(1)
BOT_ACCOUNT = 'concore-bot' #bot account name
REPO_NAME = 'concore-studies' #study repo name
UPSTREAM_ACCOUNT = 'ControlCore-Project' #upstream account name
STUDY_NAME = sys.argv[1]
STUDY_NAME_PATH = sys.argv[2]
AUTHOR_NAME = sys.argv[3]
BRANCH_NAME = sys.argv[4]
PR_TITLE = sys.argv[5]
PR_BODY = sys.argv[6]
# Defining Functions
def checkInputValidity():
if not AUTHOR_NAME or not STUDY_NAME or not STUDY_NAME_PATH:
print("Please Provide necessary Inputs")
exit(1)
if not os.path.isdir(STUDY_NAME_PATH):
print("Directory does not Exists.Invalid Path")
exit(1)
# Retry + backoff wrapper for PyGithub operations
def with_retry(operation, retries=3):
"""Retry wrapper for PyGithub operations with exponential backoff."""
for attempt in range(retries):
try:
return operation()
except github.GithubException as e:
if (e.status == 429 or e.status >= 500) and attempt < retries - 1:
wait_time = 2 ** attempt
time.sleep(wait_time)
continue
raise
print("Error: GitHub API request failed after retries.")
sys.exit(1)
# Correct PR URL (singular 'pull' not 'pulls')
def printPR(pr):
print(f'Check your example here https://github.com/{UPSTREAM_ACCOUNT}/{REPO_NAME}/pull/{pr.number}',end="")
def anyOpenPR(upstream_repo):
try:
prs = upstream_repo.get_pulls(state='open', head=f'{BOT_ACCOUNT}:{BRANCH_NAME}')
return prs[0] if prs.totalCount > 0 else None
except github.GithubException as e:
if e.status == 429 or e.status >= 500:
print("GitHub API rate limit or server error while fetching PR status.")
else:
print("Unable to fetch PR status. Try again later.")
exit(1)
except Exception:
print("Unable to fetch PR status. Try again later.")
exit(1)
def commitAndUpdateRef(repo,tree_content,commit,branch):
try:
new_tree = repo.create_git_tree(tree=tree_content,base_tree=commit.commit.tree)
new_commit = repo.create_git_commit(f"Committing Study Named {STUDY_NAME}",new_tree,[commit.commit])
if len(repo.compare(base=commit.commit.sha,head=new_commit.sha).files) == 0:
print("Your don't have any new changes.May be your example is already accepted.If this is not the case try with different fields.")
exit(1)
ref = repo.get_git_ref("heads/"+branch.name)
ref.edit(new_commit.sha,True)
except github.GithubException as e:
print(f"GitHub API error: {e.status}")
exit(1)
except Exception:
print("Failed to upload your example. Please try after some time.",end="")
exit(1)
def appendBlobInTree(repo,content,file_path,tree_content):
blob = repo.create_git_blob(content,'utf-8')
tree_content.append( github.InputGitTreeElement(path=file_path,mode="100644",type="blob",sha=blob.sha))
def runWorkflow(repo,upstream_repo):
openPR = anyOpenPR(upstream_repo)
if not openPR:
try:
repo.get_workflow("pull_request.yml").create_dispatch(
ref=BRANCH_NAME,
inputs={'title': f"[BOT]: {PR_TITLE}", 'body': PR_BODY, 'upstreamRepo': UPSTREAM_ACCOUNT, 'botRepo': BOT_ACCOUNT, 'repo': REPO_NAME}
)
printPRStatus(upstream_repo)
except github.GithubException as e:
print(f"GitHub API error while triggering workflow: {e.status}")
exit(1)
except Exception:
print("Error triggering workflow. Try again later.")
exit(1)
else:
print(f"Successfully uploaded. Waiting for approval: https://github.com/{UPSTREAM_ACCOUNT}/{REPO_NAME}/pull/{openPR.number}")
def printPRStatus(upstream_repo):
attempts = 5
delay = 2
for i in range(attempts):
print(f"Attempt: {i}")
try:
latest_pr = upstream_repo.get_pulls(state='open', sort='created', direction='desc')[0]
print(f"Check your example here: https://github.com/{UPSTREAM_ACCOUNT}/{REPO_NAME}/pull/{latest_pr.number}")
return
except Exception:
time.sleep(delay)
delay *= 2
print("Uploaded successfully, but unable to fetch status.")
def isImageFile(filename):
image_extensions = ['.jpeg', '.jpg', '.png','.gif']
return any(filename.endswith(ext) for ext in image_extensions)
def remove_prefix(text, prefix):
if text.startswith(prefix):
return text[len(prefix):]
return text
# check if directory path is Valid
checkInputValidity()
# Authenticating Github with Access token
try:
BRANCH_NAME = AUTHOR_NAME.replace(" ", "_") + "_" + STUDY_NAME if BRANCH_NAME == "#" else BRANCH_NAME.replace(" ", "_")
PR_TITLE = f"Contributing Study {STUDY_NAME} by {AUTHOR_NAME}" if PR_TITLE == "#" else PR_TITLE
PR_BODY = f"Study Name: {STUDY_NAME}\nAuthor Name: {AUTHOR_NAME}" if PR_BODY == "#" else PR_BODY
DIR_PATH = STUDY_NAME
DIR_PATH = DIR_PATH.replace(" ","_")
g = Github(BOT_TOKEN)
repo = g.get_user(BOT_ACCOUNT).get_repo(REPO_NAME)
upstream_repo = g.get_repo(f'{UPSTREAM_ACCOUNT}/{REPO_NAME}') #controlcore-Project/concore-studies
base_ref = upstream_repo.get_branch(repo.default_branch)
try:
repo.get_branch(BRANCH_NAME)
is_present = True
except github.GithubException:
print(f"No Branch is available with the name {BRANCH_NAME}")
is_present = False
except github.GithubException as e:
print(f"GitHub API error during authentication: {e.status}")
exit(1)
except Exception:
print("Authentication failed", end="")
exit(1)
try:
if not is_present:
repo.create_git_ref(f"refs/heads/{BRANCH_NAME}", base_ref.commit.sha)
branch = repo.get_branch(BRANCH_NAME)
except Exception:
print("Unable to create study. Try again later.")
exit(1)
tree_content = []
try:
for root, dirs, files in os.walk(STUDY_NAME_PATH):
files = [f for f in files if not f[0] == '.']
for filename in files:
path = f"{root}/{filename}"
if isImageFile(filename):
with open(file=path, mode='rb') as file:
image = file.read()
content = base64.b64encode(image).decode('utf-8')
else:
with open(file=path, mode='r') as file:
content = file.read()
file_path = f'{DIR_PATH+remove_prefix(path,STUDY_NAME_PATH)}'
if(platform.uname()[0]=='Windows'): file_path=file_path.replace("\\","/")
appendBlobInTree(repo,content,file_path,tree_content)
commitAndUpdateRef(repo,tree_content,base_ref.commit,branch)
runWorkflow(repo,upstream_repo)
except github.GithubException as e:
print(f"GitHub API error: {e.status}")
exit(1)
except Exception:
print("Some error occurred. Please try again after some time.",end="")
exit(1)