1+ # This script can create an empty PR in your target repo based on your specified source language PR.
2+ # Before running this script:
3+ # 1. Get a GitHub personal access token (https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) and save it in a text file.
4+ # 2. Ensure that you have forked your target repo because the changes in the new PR will be committed to your forked repository first.
5+
6+ import requests
7+ import base64
8+ import re
9+
10+ source_pr_url = "https://github.com/pingcap/docs-cn/pull/13895" # update this URL to your PR link
11+ my_github_id = "qiancai" # update this ID to your GitHub ID
12+ my_github_token_file_path = r"/Users/grcai/Documents/PingCAP/Python_scripts/GitHub/gh_token5.txt"
13+
14+ # Read the GitHub personal access token from your local file.
15+ with open (my_github_token_file_path , "r" ) as f :
16+ access_token = f .read ().strip ()
17+
18+ # For getting the information about the source language PR
19+ def get_pr_info (pr_url ):
20+ url_parts = pr_url .split ("/" )
21+ source_repo_owner = url_parts [3 ]
22+ source_repo_name = url_parts [4 ]
23+ pr_number = url_parts [6 ]
24+
25+ url = f"https://api.github.com/repos/{ source_repo_owner } /{ source_repo_name } /pulls/{ pr_number } "
26+ headers = {"Accept" : "application/vnd.github.v3+json" }
27+
28+ response = requests .get (url , headers = headers )
29+ try :
30+ response .raise_for_status ()
31+ pr_data = response .json ()
32+
33+ source_title = pr_data ["title" ]
34+ source_description = pr_data ["body" ]
35+ exclude_labels = ["size" , "translation" , "status" , "first-time-contributor" , "contribution" ]
36+ source_labels = [label ["name" ] for label in pr_data .get ("labels" , []) if not any (exclude_label in label ["name" ] for exclude_label in exclude_labels )]
37+
38+ base_repo = pr_data ["base" ]["repo" ]["full_name" ]
39+ base_branch = pr_data ["base" ]["ref" ]
40+ head_repo = pr_data ["head" ]["repo" ]["full_name" ]
41+ head_branch = pr_data ["head" ]["ref" ]
42+
43+ print (f"Getting source language PR information was successful. The head branch name is: { head_branch } " )
44+
45+ return source_title , source_description , source_labels , base_repo , base_branch , head_repo , head_branch , pr_number
46+ except requests .exceptions .HTTPError as e :
47+ print (f"Failed to get source language PR information: { response .text } " )
48+ raise e
49+
50+ # For syncing the corresponding branch of my forked repository to the latest upstream
51+ def sync_my_repo_branch (target_repo_owner , target_repo_name ,my_repo_owner , my_repo_name , base_branch ):
52+ # Get the branch reference SHA of the upstream repository.
53+ api_url = f"https://api.github.com/repos/{ target_repo_owner } /{ target_repo_name } /git/refs/heads/{ base_branch } "
54+ headers = {"Authorization" : f"Bearer { access_token } " }
55+
56+ response = requests .get (api_url , headers = headers )
57+
58+ if response .status_code == 200 :
59+ upstream_sha = response .json ().get ("object" , {}).get ("sha" )
60+ else :
61+ print ("Failed to get the upstream repository branch reference." )
62+ exit ()
63+
64+ # Update the branch reference of your fork repository
65+ api_url = f"https://api.github.com/repos/{ my_repo_owner } /{ my_repo_name } /git/refs/heads/{ base_branch } "
66+ data = {
67+ "sha" : upstream_sha ,
68+ "force" : True
69+ }
70+
71+ print ("Syncing the latest content from the upstream branch..." )
72+
73+ response = requests .patch (api_url , headers = headers , json = data )
74+
75+ if response .status_code == 200 :
76+ print ("The content sync is successful!" )
77+ else :
78+ print ("Failed to sync the latest content from the upstream branch." )
79+
80+
81+ # For creating a new branch in my forked repository
82+ def create_branch (repo_owner , repo_name , branch_name , base_branch , access_token ):
83+ api_url = f"https://api.github.com/repos/{ repo_owner } /{ repo_name } /git/refs"
84+ headers = {"Authorization" : f"Bearer { access_token } " }
85+
86+ # Get a reference to the base branch
87+ base_branch_url = f"heads/{ base_branch } "
88+ response = requests .get (f"{ api_url } /{ base_branch_url } " , headers = headers )
89+
90+ if response .status_code == 200 :
91+ base_branch_ref = response .json ().get ("object" , {}).get ("sha" )
92+
93+ if base_branch_ref :
94+ # Create a reference to a new branch
95+ branch_ref = f"refs/heads/{ branch_name } "
96+ data = {
97+ "ref" : branch_ref ,
98+ "sha" : base_branch_ref
99+ }
100+
101+ response = requests .post (api_url , headers = headers , json = data )
102+
103+ if response .status_code == 201 :
104+ branch_url = f"https://github.com/{ repo_owner } /{ repo_name } /tree/{ branch_name } "
105+ print (f"A new branch is created successfully. The branch address is: { branch_url } " )
106+ return branch_url
107+ else :
108+ print (f"Failed to create the branch: { response .text } " )
109+ raise requests .exceptions .HTTPError (response .text )
110+ else :
111+ print ("Base branch reference not found." )
112+ raise ValueError ("Base branch reference not found." )
113+ else :
114+ print (f"Failed to get base branch reference: { response .text } " )
115+ raise requests .exceptions .HTTPError (response .text )
116+
117+ # For adding a temporary temp.md file to the new branch
118+ def create_file_in_branch (repo_owner , repo_name , branch_name , access_token , file_path , file_content , commit_message ):
119+ url = f"https://api.github.com/repos/{ repo_owner } /{ repo_name } /contents/{ file_path } "
120+ headers = {
121+ "Authorization" : f"Bearer { access_token } " ,
122+ "Accept" : "application/vnd.github.v3+json"
123+ }
124+ data = {
125+ "message" : commit_message ,
126+ "branch" : branch_name ,
127+ "content" : base64 .b64encode (file_content .encode ()).decode ()
128+ }
129+ response = requests .put (url , headers = headers , json = data )
130+ response .raise_for_status ()
131+ print ("A temp file is created successfully!" )
132+
133+ # For creating a PR in the target repository and adding labels to the target PR
134+ def create_pull_request (target_repo_owner , target_repo_name , base_branch , my_repo_owner , my_repo_name , new_branch_name , access_token , title , body , labels ):
135+ url = f"https://api.github.com/repos/{ target_repo_owner } /{ target_repo_name } /pulls"
136+ headers = {
137+ "Accept" : "application/vnd.github.v3+json" ,
138+ "Authorization" : f"Bearer { access_token } "
139+ }
140+ data = {
141+ "title" : title ,
142+ "body" : body ,
143+ "head" : f"{ my_repo_owner } :{ new_branch_name } " ,
144+ "base" : base_branch
145+ }
146+ response = requests .post (url , headers = headers , json = data )
147+ try :
148+ response .raise_for_status ()
149+ pr_data = response .json ()
150+ pr_url = pr_data ["html_url" ]
151+ print (f"Your target PR is created successfully. The PR address is: { pr_url } " )
152+ url_parts = pr_url .split ("/" )
153+ pr_number = url_parts [6 ]
154+ # Add labels to the created PR
155+ label_url = f"https://api.github.com/repos/{ target_repo_owner } /{ target_repo_name } /issues/{ pr_number } /labels"
156+ payload = labels # which is an array containing the labels to be added
157+ response_labels = requests .post (label_url , headers = headers , json = payload )
158+ if response_labels .status_code == 200 :
159+ print ("Labels are added successfully." )
160+ else :
161+ print ("Failed to add labels." )
162+ return pr_url
163+
164+ except requests .exceptions .HTTPError as e :
165+ print (f"Fails to create the target PR: { response .text } " )
166+ raise e
167+
168+ # For changing the description of the translation PR
169+ def update_pr_description (source_description ):
170+
171+ source_pr_CLA = "https://cla-assistant.io/pingcap/" + base_repo
172+ new_pr_CLA = "https://cla-assistant.io/pingcap/" + target_repo_name
173+ new_pr_description = source_description .replace (source_pr_CLA , new_pr_CLA )
174+
175+ new_pr_description = new_pr_description .replace ("This PR is translated from:" , "This PR is translated from: " + source_pr_url )
176+
177+ if "tips for choosing the affected versions" in source_description :
178+ new_pr_description = re .sub (r'.*?\[tips for choosing the affected version.*?\n\n?' ,"" ,new_pr_description )
179+
180+ return new_pr_description
181+
182+ # For deleting temp.md
183+ def delete_file_in_branch (repo_owner , repo_name , branch_name , access_token , file_path , commit_message ):
184+ url = f"https://api.github.com/repos/{ repo_owner } /{ repo_name } /contents/{ file_path } "
185+ headers = {
186+ "Authorization" : f"Bearer { access_token } " ,
187+ "Accept" : "application/vnd.github.v3+json"
188+ }
189+ params = {
190+ "ref" : branch_name
191+ }
192+
193+ # Get information about the file
194+ response = requests .get (url , headers = headers , params = params )
195+
196+ if response .status_code == 200 :
197+
198+ file_info = response .json ()
199+
200+ # Prepare the request data to delete the file
201+ data = {
202+ "message" : commit_message ,
203+ "sha" : file_info ["sha" ],
204+ "branch" : branch_name
205+ }
206+
207+ # Send a request to delete a file
208+ try :
209+ response = requests .delete (url , headers = headers , params = params , json = data )
210+ response .raise_for_status ()
211+ print ("The temp.md is deleted successfully!" )
212+ except requests .exceptions .HTTPError as e :
213+ print (f"Failed to delete temp.md. Error message: { response .text } " )
214+ raise e
215+ elif response .status_code == 404 :
216+ print (f"The temp.md file does not exist in branch { branch_name } ." )
217+ else :
218+ print (f"Failed to get file information. Error message: { response .text } " )
219+
220+ # Get the information of the source language PR
221+ source_title , source_description , source_labels , base_repo , base_branch , head_repo , head_branch , source_pr_number = get_pr_info (source_pr_url )
222+
223+ # Get the repo info of my foked repository and the target repository, and the translation label
224+ my_repo_owner = my_github_id
225+ target_repo_owner = "pingcap"
226+ if "pingcap/docs-cn/pull" in source_pr_url :
227+ my_repo_name = "docs"
228+ target_repo_name = "docs"
229+ translation_label = "translation/from-docs-cn"
230+ elif "pingcap/docs/pull" in source_pr_url :
231+ target_repo_name = "docs-cn"
232+ my_repo_name = "docs-cn"
233+ translation_label = "translation/from-docs"
234+ else :
235+ print ("Error: The provided URL is not a pull request of pingcap/docs-cn or pingcap/docs." )
236+ print ("Exiting the program..." )
237+ exit (1 )
238+
239+ source_labels .append (translation_label )
240+ #print ("The following labels will be reused for the translation PR.")
241+ #print (source_labels)
242+
243+ # Sync from upstream
244+ sync_my_repo_branch (target_repo_owner , target_repo_name ,my_repo_owner , my_repo_name , base_branch )
245+
246+ # Create a new branch in the repository that I forked
247+ new_branch_name = head_branch + "-" + source_pr_number
248+ ## print (my_repo_owner, my_repo_name, new_branch_name, base_branch )
249+
250+ create_branch (my_repo_owner , my_repo_name , new_branch_name , base_branch , access_token )
251+
252+ # Create a temporary temp.md file in the new branch
253+ file_path = "temp.md"
254+ file_content = "This is a test file."
255+ commit_message = "Add temp.md"
256+ create_file_in_branch (my_repo_owner , my_repo_name , new_branch_name , access_token , file_path , file_content , commit_message )
257+
258+ # Create the target PR
259+ title = source_title
260+ body = update_pr_description (source_description )
261+ labels = source_labels
262+ create_pull_request (target_repo_owner , target_repo_name , base_branch , my_repo_owner , my_repo_name , new_branch_name , access_token , title , body , labels )
263+
264+ # Delete the temporary temp.md file
265+ commit_message2 = "Delete temp.md"
266+ delete_file_in_branch (my_repo_owner , my_repo_name , new_branch_name , access_token , file_path , commit_message2 )
0 commit comments