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