1- import github
2- from github import Github
3- import os ,sys ,platform ,base64 ,time
4-
5- # Intializing the Variables
6- BOT_TOKEN = os .environ .get ('CONCORE_BOT_TOKEN' , '' )
7- BOT_ACCOUNT = 'concore-bot' #bot account name
8- REPO_NAME = 'concore-studies' #study repo name
9- UPSTREAM_ACCOUNT = 'ControlCore-Project' #upstream account name
10- STUDY_NAME = sys .argv [1 ]
11- STUDY_NAME_PATH = sys .argv [2 ]
12- AUTHOR_NAME = sys .argv [3 ]
13- BRANCH_NAME = sys .argv [4 ]
14- PR_TITLE = sys .argv [5 ]
15- PR_BODY = sys .argv [6 ]
16-
17- # Defining Functions
18- def checkInputValidity ():
19- if not AUTHOR_NAME or not STUDY_NAME or not STUDY_NAME_PATH :
20- print ("Please Provide necessary Inputs" )
21- exit (1 )
22- if not os .path .isdir (STUDY_NAME_PATH ):
23- print ("Directory doesnot Exists.Invalid Path" )
24- exit (1 )
25-
26- def printPR (pr ):
27- print (f'Check your example here https://github.com/{ UPSTREAM_ACCOUNT } /{ REPO_NAME } /pulls/{ pr .number } ' ,end = "" )
28-
29- def anyOpenPR (upstream_repo ):
30- try :
31- prs = upstream_repo .get_pulls (state = 'open' , head = f'{ BOT_ACCOUNT } :{ BRANCH_NAME } ' )
32- return prs [0 ] if prs .totalCount > 0 else None
33- except Exception :
34- print ("Unable to fetch PR status. Try again later." )
35- exit (1 )
36-
37- def commitAndUpdateRef (repo ,tree_content ,commit ,branch ):
38- try :
39- new_tree = repo .create_git_tree (tree = tree_content ,base_tree = commit .commit .tree )
40- new_commit = repo .create_git_commit (f"Committing Study Named { STUDY_NAME } " ,new_tree ,[commit .commit ])
41- if len (repo .compare (base = commit .commit .sha ,head = new_commit .sha ).files ) == 0 :
42- 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." )
43- exit (1 )
44- ref = repo .get_git_ref ("heads/" + branch .name )
45- ref .edit (new_commit .sha ,True )
46- except Exception as e :
47- print ("failed to Upload your example.Please try after some time." ,end = "" )
48- exit (1 )
49-
50-
51- def appendBlobInTree (repo ,content ,file_path ,tree_content ):
52- blob = repo .create_git_blob (content ,'utf-8' )
53- tree_content .append ( github .InputGitTreeElement (path = file_path ,mode = "100644" ,type = "blob" ,sha = blob .sha ))
54-
55-
56- def runWorkflow (repo ,upstream_repo ):
57- openPR = anyOpenPR (upstream_repo )
58- if not openPR :
59- try :
60- repo .get_workflow ("pull_request.yml" ).create_dispatch (
61- ref = BRANCH_NAME ,
62- inputs = {'title' : f"[BOT]: { PR_TITLE } " , 'body' : PR_BODY , 'upstreamRepo' : UPSTREAM_ACCOUNT , 'botRepo' : BOT_ACCOUNT , 'repo' : REPO_NAME }
63- )
64- printPRStatus (upstream_repo )
65- except Exception as e :
66- print (f"Error triggering workflow. Try again later.\n ERROR: { e } " )
67- exit (1 )
68- else :
69- print (f"Successfully uploaded. Waiting for approval: https://github.com/{ UPSTREAM_ACCOUNT } /{ REPO_NAME } /pull/{ openPR .number } " )
70-
71- def printPRStatus (upstream_repo ):
72- attempts = 5
73- delay = 2
74- for i in range (attempts ):
75- print (f"Attempt: { i } " )
76- try :
77- latest_pr = upstream_repo .get_pulls (state = 'open' , sort = 'created' , direction = 'desc' )[0 ]
78- print (f"Check your example here: https://github.com/{ UPSTREAM_ACCOUNT } /{ REPO_NAME } /pull/{ latest_pr .number } " )
79- return
80- except Exception :
81- time .sleep (delay )
82- delay *= 2
83- print ("Uploaded successfully, but unable to fetch status." )
84-
85-
86- def isImageFile (filename ):
87- image_extensions = ['.jpeg' , '.jpg' , '.png' ,'.gif' ]
88- return any (filename .endswith (ext ) for ext in image_extensions )
89-
90- def remove_prefix (text , prefix ):
91- if text .startswith (prefix ):
92- return text [len (prefix ):]
93- return text
94-
95-
96- # Decode Github Token
97- def decode_token (encoded_token ):
98- decoded_bytes = encoded_token .encode ("ascii" )
99- convertedbytes = base64 .b64decode (decoded_bytes )
100- decoded_token = convertedbytes .decode ("ascii" )
101- return decoded_token
102-
103-
104- # check if directory path is Valid
105- checkInputValidity ()
106-
107-
108- # Authenticating Github with Access token
109- try :
110- BRANCH_NAME = AUTHOR_NAME .replace (" " , "_" ) + "_" + STUDY_NAME if BRANCH_NAME == "#" else BRANCH_NAME .replace (" " , "_" )
111- PR_TITLE = f"Contributing Study { STUDY_NAME } by { AUTHOR_NAME } " if PR_TITLE == "#" else PR_TITLE
112- PR_BODY = f"Study Name: { STUDY_NAME } \n Author Name: { AUTHOR_NAME } " if PR_BODY == "#" else PR_BODY
113- DIR_PATH = STUDY_NAME
114- DIR_PATH = DIR_PATH .replace (" " ,"_" )
115- g = Github (BOT_TOKEN )
116- repo = g .get_user (BOT_ACCOUNT ).get_repo (REPO_NAME )
117- upstream_repo = g .get_repo (f'{ UPSTREAM_ACCOUNT } /{ REPO_NAME } ' ) #controlcore-Project/concore-studies
118- base_ref = upstream_repo .get_branch (repo .default_branch )
119-
120- try :
121- repo .get_branch (BRANCH_NAME )
122- is_present = True
123- except github .GithubException :
124- print (f"No Branch is available with the name { BRANCH_NAME } " )
125- is_present = False
126- except Exception as e :
127- print ("Authentication failed" , end = "" )
128- exit (1 )
129-
130-
131- try :
132- if not is_present :
133- repo .create_git_ref (f"refs/heads/{ BRANCH_NAME } " , base_ref .commit .sha )
134- branch = repo .get_branch (BRANCH_NAME )
135- except Exception :
136- print ("Unable to create study. Try again later." )
137- exit (1 )
138-
139-
140- tree_content = []
141-
142- try :
143- for root , dirs , files in os .walk (STUDY_NAME_PATH ):
144- files = [f for f in files if not f [0 ] == '.' ]
145- for filename in files :
146- path = f"{ root } /{ filename } "
147- if isImageFile (filename ):
148- with open (file = path , mode = 'rb' ) as file :
149- image = file .read ()
150- content = base64 .b64encode (image ).decode ('utf-8' )
151- else :
152- with open (file = path , mode = 'r' ) as file :
153- content = file .read ()
154- file_path = f'{ DIR_PATH + remove_prefix (path ,STUDY_NAME_PATH )} '
155- if (platform .uname ()[0 ]== 'Windows' ): file_path = file_path .replace ("\\ " ,"/" )
156- appendBlobInTree (repo ,content ,file_path ,tree_content )
157- commitAndUpdateRef (repo ,tree_content ,base_ref .commit ,branch )
158- runWorkflow (repo ,upstream_repo )
159- except Exception as e :
160- print (e )
161- print ("Some error Occured.Please try again after some time." ,end = "" )
1+ import github
2+ from github import Github
3+ import os ,sys ,platform ,base64 ,time ,re
4+
5+ # Initializing the Variables
6+ BOT_TOKEN = os .environ .get ('CONCORE_BOT_TOKEN' , '' )
7+
8+ # Fail fast if token is missing
9+ if not BOT_TOKEN :
10+ print ("Error: CONCORE_BOT_TOKEN environment variable is not set." )
11+ sys .exit (1 )
12+
13+ # Token format validation
14+ token_pattern = r"^((ghp_|github_pat_|ghs_)[A-Za-z0-9_]{20,}|[0-9a-fA-F]{40})$"
15+ if not re .match (token_pattern , BOT_TOKEN ):
16+ print ("Error: Invalid GitHub token format." )
17+ sys .exit (1 )
18+ BOT_ACCOUNT = 'concore-bot' #bot account name
19+ REPO_NAME = 'concore-studies' #study repo name
20+ UPSTREAM_ACCOUNT = 'ControlCore-Project' #upstream account name
21+ STUDY_NAME = sys .argv [1 ]
22+ STUDY_NAME_PATH = sys .argv [2 ]
23+ AUTHOR_NAME = sys .argv [3 ]
24+ BRANCH_NAME = sys .argv [4 ]
25+ PR_TITLE = sys .argv [5 ]
26+ PR_BODY = sys .argv [6 ]
27+
28+ # Defining Functions
29+ def checkInputValidity ():
30+ if not AUTHOR_NAME or not STUDY_NAME or not STUDY_NAME_PATH :
31+ print ("Please Provide necessary Inputs" )
32+ exit (1 )
33+ if not os .path .isdir (STUDY_NAME_PATH ):
34+ print ("Directory does not Exists.Invalid Path" )
35+ exit (1 )
36+
37+ # Retry + backoff wrapper for PyGithub operations
38+ def with_retry (operation , retries = 3 ):
39+ """Retry wrapper for PyGithub operations with exponential backoff."""
40+ for attempt in range (retries ):
41+ try :
42+ return operation ()
43+ except github .GithubException as e :
44+ if (e .status == 429 or e .status >= 500 ) and attempt < retries - 1 :
45+ wait_time = 2 ** attempt
46+ time .sleep (wait_time )
47+ continue
48+ raise
49+ print ("Error: GitHub API request failed after retries." )
50+ sys .exit (1 )
51+
52+ # Correct PR URL (singular 'pull' not 'pulls')
53+ def printPR (pr ):
54+ print (f'Check your example here https://github.com/{ UPSTREAM_ACCOUNT } /{ REPO_NAME } /pull/{ pr .number } ' ,end = "" )
55+
56+ def anyOpenPR (upstream_repo ):
57+ try :
58+ prs = upstream_repo .get_pulls (state = 'open' , head = f'{ BOT_ACCOUNT } :{ BRANCH_NAME } ' )
59+ return prs [0 ] if prs .totalCount > 0 else None
60+ except github .GithubException as e :
61+ if e .status == 429 or e .status >= 500 :
62+ print ("GitHub API rate limit or server error while fetching PR status." )
63+ else :
64+ print ("Unable to fetch PR status. Try again later." )
65+ exit (1 )
66+ except Exception :
67+ print ("Unable to fetch PR status. Try again later." )
68+ exit (1 )
69+
70+ def commitAndUpdateRef (repo ,tree_content ,commit ,branch ):
71+ try :
72+ new_tree = repo .create_git_tree (tree = tree_content ,base_tree = commit .commit .tree )
73+ new_commit = repo .create_git_commit (f"Committing Study Named { STUDY_NAME } " ,new_tree ,[commit .commit ])
74+ if len (repo .compare (base = commit .commit .sha ,head = new_commit .sha ).files ) == 0 :
75+ 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." )
76+ exit (1 )
77+ ref = repo .get_git_ref ("heads/" + branch .name )
78+ ref .edit (new_commit .sha ,True )
79+ except github .GithubException as e :
80+ print (f"GitHub API error: { e .status } " )
81+ exit (1 )
82+ except Exception :
83+ print ("Failed to upload your example. Please try after some time." ,end = "" )
84+ exit (1 )
85+
86+
87+ def appendBlobInTree (repo ,content ,file_path ,tree_content ):
88+ blob = repo .create_git_blob (content ,'utf-8' )
89+ tree_content .append ( github .InputGitTreeElement (path = file_path ,mode = "100644" ,type = "blob" ,sha = blob .sha ))
90+
91+
92+ def runWorkflow (repo ,upstream_repo ):
93+ openPR = anyOpenPR (upstream_repo )
94+ if not openPR :
95+ try :
96+ repo .get_workflow ("pull_request.yml" ).create_dispatch (
97+ ref = BRANCH_NAME ,
98+ inputs = {'title' : f"[BOT]: { PR_TITLE } " , 'body' : PR_BODY , 'upstreamRepo' : UPSTREAM_ACCOUNT , 'botRepo' : BOT_ACCOUNT , 'repo' : REPO_NAME }
99+ )
100+ printPRStatus (upstream_repo )
101+ except github .GithubException as e :
102+ print (f"GitHub API error while triggering workflow: { e .status } " )
103+ exit (1 )
104+ except Exception :
105+ print ("Error triggering workflow. Try again later." )
106+ exit (1 )
107+ else :
108+ print (f"Successfully uploaded. Waiting for approval: https://github.com/{ UPSTREAM_ACCOUNT } /{ REPO_NAME } /pull/{ openPR .number } " )
109+
110+ def printPRStatus (upstream_repo ):
111+ attempts = 5
112+ delay = 2
113+ for i in range (attempts ):
114+ print (f"Attempt: { i } " )
115+ try :
116+ latest_pr = upstream_repo .get_pulls (state = 'open' , sort = 'created' , direction = 'desc' )[0 ]
117+ print (f"Check your example here: https://github.com/{ UPSTREAM_ACCOUNT } /{ REPO_NAME } /pull/{ latest_pr .number } " )
118+ return
119+ except Exception :
120+ time .sleep (delay )
121+ delay *= 2
122+ print ("Uploaded successfully, but unable to fetch status." )
123+
124+
125+ def isImageFile (filename ):
126+ image_extensions = ['.jpeg' , '.jpg' , '.png' ,'.gif' ]
127+ return any (filename .endswith (ext ) for ext in image_extensions )
128+
129+ def remove_prefix (text , prefix ):
130+ if text .startswith (prefix ):
131+ return text [len (prefix ):]
132+ return text
133+
134+
135+ # check if directory path is Valid
136+ checkInputValidity ()
137+
138+
139+ # Authenticating Github with Access token
140+ try :
141+ BRANCH_NAME = AUTHOR_NAME .replace (" " , "_" ) + "_" + STUDY_NAME if BRANCH_NAME == "#" else BRANCH_NAME .replace (" " , "_" )
142+ PR_TITLE = f"Contributing Study { STUDY_NAME } by { AUTHOR_NAME } " if PR_TITLE == "#" else PR_TITLE
143+ PR_BODY = f"Study Name: { STUDY_NAME } \n Author Name: { AUTHOR_NAME } " if PR_BODY == "#" else PR_BODY
144+ DIR_PATH = STUDY_NAME
145+ DIR_PATH = DIR_PATH .replace (" " ,"_" )
146+ g = Github (BOT_TOKEN )
147+ repo = g .get_user (BOT_ACCOUNT ).get_repo (REPO_NAME )
148+ upstream_repo = g .get_repo (f'{ UPSTREAM_ACCOUNT } /{ REPO_NAME } ' ) #controlcore-Project/concore-studies
149+ base_ref = upstream_repo .get_branch (repo .default_branch )
150+
151+ try :
152+ repo .get_branch (BRANCH_NAME )
153+ is_present = True
154+ except github .GithubException :
155+ print (f"No Branch is available with the name { BRANCH_NAME } " )
156+ is_present = False
157+ except github .GithubException as e :
158+ print (f"GitHub API error during authentication: { e .status } " )
159+ exit (1 )
160+ except Exception :
161+ print ("Authentication failed" , end = "" )
162+ exit (1 )
163+
164+
165+ try :
166+ if not is_present :
167+ repo .create_git_ref (f"refs/heads/{ BRANCH_NAME } " , base_ref .commit .sha )
168+ branch = repo .get_branch (BRANCH_NAME )
169+ except Exception :
170+ print ("Unable to create study. Try again later." )
171+ exit (1 )
172+
173+
174+ tree_content = []
175+
176+ try :
177+ for root , dirs , files in os .walk (STUDY_NAME_PATH ):
178+ files = [f for f in files if not f [0 ] == '.' ]
179+ for filename in files :
180+ path = f"{ root } /{ filename } "
181+ if isImageFile (filename ):
182+ with open (file = path , mode = 'rb' ) as file :
183+ image = file .read ()
184+ content = base64 .b64encode (image ).decode ('utf-8' )
185+ else :
186+ with open (file = path , mode = 'r' ) as file :
187+ content = file .read ()
188+ file_path = f'{ DIR_PATH + remove_prefix (path ,STUDY_NAME_PATH )} '
189+ if (platform .uname ()[0 ]== 'Windows' ): file_path = file_path .replace ("\\ " ,"/" )
190+ appendBlobInTree (repo ,content ,file_path ,tree_content )
191+ commitAndUpdateRef (repo ,tree_content ,base_ref .commit ,branch )
192+ runWorkflow (repo ,upstream_repo )
193+ except github .GithubException as e :
194+ print (f"GitHub API error: { e .status } " )
195+ exit (1 )
196+ except Exception :
197+ print ("Some error occurred. Please try again after some time." ,end = "" )
162198 exit (1 )
0 commit comments