1717# limitations under the License.
1818#
1919
20- # Utility for creating well-formed pull request merges and pushing them to Apache.
20+ # Utility for creating well-formed pull request merges and pushing them to
21+ # Apache.
2122# usage: ./apache-pr-merge.py (see config env vars below)
2223#
2324# This utility assumes you already have a local Arrow git clone and that you
2425# have added remotes corresponding to both (i) the Github Apache Arrow mirror
2526# and (ii) the apache git repo.
2627
27- import json
2828import os
2929import re
3030import subprocess
3131import sys
32- import tempfile
33- import urllib2
32+ import requests
3433import getpass
3534
35+ from six .moves import input
36+ import six
37+
3638try :
3739 import jira .client
3840 JIRA_IMPORTED = True
4244# Location of your Arrow git clone
4345ARROW_HOME = os .path .abspath (__file__ ).rsplit ("/" , 2 )[0 ]
4446PROJECT_NAME = ARROW_HOME .rsplit ("/" , 1 )[1 ]
45- print "ARROW_HOME = " + ARROW_HOME
46- print "PROJECT_NAME = " + PROJECT_NAME
47+ print ( "ARROW_HOME = " + ARROW_HOME )
48+ print ( "PROJECT_NAME = " + PROJECT_NAME )
4749
4850# Remote name which points to the Gihub site
4951PR_REMOTE_NAME = os .environ .get ("PR_REMOTE_NAME" , "apache-github" )
6567
6668
6769def get_json (url ):
68- try :
69- from urllib2 import urlopen , Request
70- env_var = 'ARROW_GITHUB_API_TOKEN'
71-
72- if env_var in os .environ :
73- token = os .environ [env_var ]
74- request = Request (url )
75- request .add_header ('Authorization' , 'token %s' % token )
76- response = urlopen (request )
77- else :
78- response = urlopen (url )
79- return json .load (response )
80- except urllib2 .HTTPError as e :
81- print "Unable to fetch URL, exiting: %s" % url
82- sys .exit (- 1 )
70+ req = requests .get (url )
71+ return req .json ()
8372
8473
8574def fail (msg ):
86- print msg
75+ print ( msg )
8776 clean_up ()
8877 sys .exit (- 1 )
8978
9079
9180def run_cmd (cmd ):
81+ if isinstance (cmd , six .string_types ):
82+ cmd = cmd .split (' ' )
83+
9284 try :
93- if isinstance (cmd , list ):
94- return subprocess .check_output (cmd )
95- else :
96- return subprocess .check_output (cmd .split (" " ))
85+ output = subprocess .check_output (cmd )
9786 except subprocess .CalledProcessError as e :
9887 # this avoids hiding the stdout / stderr of failed processes
99- print 'Command failed: %s' % cmd
100- print 'With output:'
101- print '--------------'
102- print e .output
103- print '--------------'
88+ print ( 'Command failed: %s' % cmd )
89+ print ( 'With output:' )
90+ print ( '--------------' )
91+ print ( e .output )
92+ print ( '--------------' )
10493 raise e
10594
95+ if isinstance (output , six .binary_type ):
96+ output = output .decode ('utf-8' )
97+ return output
98+
99+
106100def continue_maybe (prompt ):
107- result = raw_input ("\n %s (y/n): " % prompt )
101+ result = input ("\n %s (y/n): " % prompt )
108102 if result .lower () != "y" :
109103 fail ("Okay, exiting" )
110104
@@ -113,46 +107,52 @@ def continue_maybe(prompt):
113107
114108
115109def clean_up ():
116- print "Restoring head pointer to %s" % original_head
110+ print ( "Restoring head pointer to %s" % original_head )
117111 run_cmd ("git checkout %s" % original_head )
118112
119113 branches = run_cmd ("git branch" ).replace (" " , "" ).split ("\n " )
120114
121- for branch in filter ( lambda x : x .startswith (BRANCH_PREFIX ), branches ) :
122- print "Deleting local branch %s" % branch
115+ for branch in [ x for x in branches if x .startswith (BRANCH_PREFIX )] :
116+ print ( "Deleting local branch %s" % branch )
123117 run_cmd ("git branch -D %s" % branch )
124118
125119
126120# merge the requested PR and return the merge hash
127121def merge_pr (pr_num , target_ref ):
128122 pr_branch_name = "%s_MERGE_PR_%s" % (BRANCH_PREFIX , pr_num )
129- target_branch_name = "%s_MERGE_PR_%s_%s" % (BRANCH_PREFIX , pr_num , target_ref .upper ())
130- run_cmd ("git fetch %s pull/%s/head:%s" % (PR_REMOTE_NAME , pr_num , pr_branch_name ))
131- run_cmd ("git fetch %s %s:%s" % (PUSH_REMOTE_NAME , target_ref , target_branch_name ))
123+ target_branch_name = "%s_MERGE_PR_%s_%s" % (BRANCH_PREFIX , pr_num ,
124+ target_ref .upper ())
125+ run_cmd ("git fetch %s pull/%s/head:%s" % (PR_REMOTE_NAME , pr_num ,
126+ pr_branch_name ))
127+ run_cmd ("git fetch %s %s:%s" % (PUSH_REMOTE_NAME , target_ref ,
128+ target_branch_name ))
132129 run_cmd ("git checkout %s" % target_branch_name )
133130
134131 had_conflicts = False
135132 try :
136133 run_cmd (['git' , 'merge' , pr_branch_name , '--squash' ])
137134 except Exception as e :
138- msg = "Error merging: %s\n Would you like to manually fix-up this merge?" % e
135+ msg = ("Error merging: %s\n Would you like to "
136+ "manually fix-up this merge?" % e )
139137 continue_maybe (msg )
140- msg = "Okay, please fix any conflicts and 'git add' conflicting files... Finished?"
138+ msg = ("Okay, please fix any conflicts and 'git add' "
139+ "conflicting files... Finished?" )
141140 continue_maybe (msg )
142141 had_conflicts = True
143142
144143 commit_authors = run_cmd (['git' , 'log' , 'HEAD..%s' % pr_branch_name ,
145144 '--pretty=format:%an <%ae>' ]).split ("\n " )
146145 distinct_authors = sorted (set (commit_authors ),
147- key = lambda x : commit_authors .count (x ), reverse = True )
146+ key = lambda x : commit_authors .count (x ),
147+ reverse = True )
148148 primary_author = distinct_authors [0 ]
149149 commits = run_cmd (['git' , 'log' , 'HEAD..%s' % pr_branch_name ,
150150 '--pretty=format:%h [%an] %s' ]).split ("\n \n " )
151151
152152 merge_message_flags = []
153153
154154 merge_message_flags += ["-m" , title ]
155- if body != None :
155+ if body is not None :
156156 merge_message_flags += ["-m" , body ]
157157
158158 authors = "\n " .join (["Author: %s" % a for a in distinct_authors ])
@@ -162,14 +162,17 @@ def merge_pr(pr_num, target_ref):
162162 if had_conflicts :
163163 committer_name = run_cmd ("git config --get user.name" ).strip ()
164164 committer_email = run_cmd ("git config --get user.email" ).strip ()
165- message = "This patch had conflicts when merged, resolved by\n Committer: %s <%s>" % (
166- committer_name , committer_email )
165+ message = ("This patch had conflicts when merged, "
166+ "resolved by\n Committer: %s <%s>" %
167+ (committer_name , committer_email ))
167168 merge_message_flags += ["-m" , message ]
168169
169- # The string "Closes #%s" string is required for GitHub to correctly close the PR
170+ # The string "Closes #%s" string is required for GitHub to correctly close
171+ # the PR
170172 merge_message_flags += [
171173 "-m" ,
172- "Closes #%s from %s and squashes the following commits:" % (pr_num , pr_repo_desc )]
174+ "Closes #%s from %s and squashes the following commits:"
175+ % (pr_num , pr_repo_desc )]
173176 for c in commits :
174177 merge_message_flags += ["-m" , c ]
175178
@@ -182,7 +185,8 @@ def merge_pr(pr_num, target_ref):
182185 target_branch_name , PUSH_REMOTE_NAME ))
183186
184187 try :
185- run_cmd ('git push %s %s:%s' % (PUSH_REMOTE_NAME , target_branch_name , target_ref ))
188+ run_cmd ('git push %s %s:%s' % (PUSH_REMOTE_NAME , target_branch_name ,
189+ target_ref ))
186190 except Exception as e :
187191 clean_up ()
188192 fail ("Exception while pushing: %s" % e )
@@ -194,65 +198,42 @@ def merge_pr(pr_num, target_ref):
194198 return merge_hash
195199
196200
197- def cherry_pick (pr_num , merge_hash , default_branch ):
198- pick_ref = raw_input ("Enter a branch name [%s]: " % default_branch )
199- if pick_ref == "" :
200- pick_ref = default_branch
201-
202- pick_branch_name = "%s_PICK_PR_%s_%s" % (BRANCH_PREFIX , pr_num , pick_ref .upper ())
203-
204- run_cmd ("git fetch %s %s:%s" % (PUSH_REMOTE_NAME , pick_ref , pick_branch_name ))
205- run_cmd ("git checkout %s" % pick_branch_name )
206- run_cmd ("git cherry-pick -sx %s" % merge_hash )
207-
208- continue_maybe ("Pick complete (local ref %s). Push to %s?" % (
209- pick_branch_name , PUSH_REMOTE_NAME ))
210-
211- try :
212- run_cmd ('git push %s %s:%s' % (PUSH_REMOTE_NAME , pick_branch_name , pick_ref ))
213- except Exception as e :
214- clean_up ()
215- fail ("Exception while pushing: %s" % e )
216-
217- pick_hash = run_cmd ("git rev-parse %s" % pick_branch_name )[:8 ]
218- clean_up ()
219-
220- print ("Pull request #%s picked into %s!" % (pr_num , pick_ref ))
221- print ("Pick hash: %s" % pick_hash )
222- return pick_ref
223-
224-
225201def fix_version_from_branch (branch , versions ):
226- # Note: Assumes this is a sorted (newest->oldest) list of un-released versions
202+ # Note: Assumes this is a sorted (newest->oldest) list of un-released
203+ # versions
227204 if branch == "master" :
228205 return versions [0 ]
229206 else :
230207 branch_ver = branch .replace ("branch-" , "" )
231- return filter (lambda x : x .name .startswith (branch_ver ), versions )[- 1 ]
208+ return [x for x in versions if x .name .startswith (branch_ver )][- 1 ]
209+
232210
233211def exctract_jira_id (title ):
234212 m = re .search (r'^(ARROW-[0-9]+)\b.*$' , title )
235213 if m and m .groups > 0 :
236214 return m .group (1 )
237215 else :
238- fail ("PR title should be prefixed by a jira id \" ARROW-XXX: ...\" , found: \" %s\" " % title )
216+ fail ("PR title should be prefixed by a jira id "
217+ "\" ARROW-XXX: ...\" , found: \" %s\" " % title )
218+
239219
240220def check_jira (title ):
241221 jira_id = exctract_jira_id (title )
242222 asf_jira = jira .client .JIRA ({'server' : JIRA_API_BASE },
243223 basic_auth = (JIRA_USERNAME , JIRA_PASSWORD ))
244224 try :
245- issue = asf_jira .issue (jira_id )
225+ asf_jira .issue (jira_id )
246226 except Exception as e :
247227 fail ("ASF JIRA could not find %s\n %s" % (jira_id , e ))
248228
229+
249230def resolve_jira (title , merge_branches , comment ):
250231 asf_jira = jira .client .JIRA ({'server' : JIRA_API_BASE },
251232 basic_auth = (JIRA_USERNAME , JIRA_PASSWORD ))
252233
253234 default_jira_id = exctract_jira_id (title )
254235
255- jira_id = raw_input ("Enter a JIRA id [%s]: " % default_jira_id )
236+ jira_id = input ("Enter a JIRA id [%s]: " % default_jira_id )
256237 if jira_id == "" :
257238 jira_id = default_jira_id
258239
@@ -271,30 +252,33 @@ def resolve_jira(title, merge_branches, comment):
271252
272253 if cur_status == "Resolved" or cur_status == "Closed" :
273254 fail ("JIRA issue %s already has status '%s'" % (jira_id , cur_status ))
274- print ("=== JIRA %s ===" % jira_id )
275- print ("summary\t \t %s\n assignee\t %s\n status\t \t %s\n url\t \t %s/%s \n " % (
276- cur_summary , cur_assignee , cur_status , JIRA_BASE , jira_id ))
255+ print ("=== JIRA %s ===" % jira_id )
256+ print ("summary\t \t %s\n assignee\t %s\n status\t \t %s\n url\t \t %s/%sf \n "
257+ % ( cur_summary , cur_assignee , cur_status , JIRA_BASE , jira_id ))
277258
278259 resolve = filter (lambda a : a ['name' ] == "Resolve Issue" ,
279260 asf_jira .transitions (jira_id ))[0 ]
280261 asf_jira .transition_issue (jira_id , resolve ["id" ], comment = comment )
281262
282- print "Succesfully resolved %s!" % (jira_id )
263+ print ( "Succesfully resolved %s!" % (jira_id ) )
283264
284265
285266if not JIRA_USERNAME :
286- JIRA_USERNAME = raw_input ("Env JIRA_USERNAME not set, please enter your JIRA username:" )
267+ JIRA_USERNAME = input ("Env JIRA_USERNAME not set, "
268+ "please enter your JIRA username:" )
287269
288270if not JIRA_PASSWORD :
289- JIRA_PASSWORD = getpass .getpass ("Env JIRA_PASSWORD not set, please enter your JIRA password:" )
271+ JIRA_PASSWORD = getpass .getpass ("Env JIRA_PASSWORD not set, please enter "
272+ "your JIRA password:" )
290273
291274branches = get_json ("%s/branches" % GITHUB_API_BASE )
292- branch_names = filter (lambda x : x .startswith ("branch-" ), [x ['name' ] for x in branches ])
275+ branch_names = [x ['name' ] for x in branches if x ['name' ].startswith ('branch-' )]
276+
293277# Assumes branch names can be sorted lexicographically
294278# Julien: I commented this out as we don't have any "branch-*" branch yet
295- #latest_branch = sorted(branch_names, reverse=True)[0]
279+ # latest_branch = sorted(branch_names, reverse=True)[0]
296280
297- pr_num = raw_input ("Which pull request would you like to merge? (e.g. 34): " )
281+ pr_num = input ("Which pull request would you like to merge? (e.g. 34): " )
298282pr = get_json ("%s/pulls/%s" % (GITHUB_API_BASE , pr_num ))
299283
300284url = pr ["url" ]
@@ -307,42 +291,41 @@ def resolve_jira(title, merge_branches, comment):
307291pr_repo_desc = "%s/%s" % (user_login , base_ref )
308292
309293if pr ["merged" ] is True :
310- print "Pull request %s has already been merged, assuming you want to backport" % pr_num
294+ print ("Pull request %s has already been merged, "
295+ "assuming you want to backport" % pr_num )
311296 merge_commit_desc = run_cmd ([
312297 'git' , 'log' , '--merges' , '--first-parent' ,
313298 '--grep=pull request #%s' % pr_num , '--oneline' ]).split ("\n " )[0 ]
314299 if merge_commit_desc == "" :
315- fail ("Couldn't find any merge commit for #%s, you may need to update HEAD." % pr_num )
300+ fail ("Couldn't find any merge commit for #%s, "
301+ "you may need to update HEAD." % pr_num )
316302
317303 merge_hash = merge_commit_desc [:7 ]
318304 message = merge_commit_desc [8 :]
319305
320- print "Found: %s" % message
321- maybe_cherry_pick (pr_num , merge_hash , latest_branch )
306+ print ("Found: %s" % message )
322307 sys .exit (0 )
323308
324309if not bool (pr ["mergeable" ]):
325- msg = "Pull request %s is not mergeable in its current form.\n " % pr_num + \
326- "Continue? (experts only!)"
310+ msg = ( "Pull request %s is not mergeable in its current form.\n "
311+ % pr_num + "Continue? (experts only!)" )
327312 continue_maybe (msg )
328313
329- print ("\n === Pull Request #%s ===" % pr_num )
330- print ("title\t %s\n source\t %s\n target\t %s\n url\t %s" % (
331- title , pr_repo_desc , target_ref , url ))
314+ print ("\n === Pull Request #%s ===" % pr_num )
315+ print ("title\t %s\n source\t %s\n target\t %s\n url\t %s"
316+ % ( title , pr_repo_desc , target_ref , url ))
332317continue_maybe ("Proceed with merging pull request #%s?" % pr_num )
333318
334319merged_refs = [target_ref ]
335320
336321merge_hash = merge_pr (pr_num , target_ref )
337322
338- pick_prompt = "Would you like to pick %s into another branch?" % merge_hash
339- while raw_input ("\n %s (y/n): " % pick_prompt ).lower () == "y" :
340- merged_refs = merged_refs + [cherry_pick (pr_num , merge_hash , latest_branch )]
341-
342323if JIRA_IMPORTED :
343324 continue_maybe ("Would you like to update the associated JIRA?" )
344- jira_comment = "Issue resolved by pull request %s\n [%s/%s]" % (pr_num , GITHUB_BASE , pr_num )
325+ jira_comment = ("Issue resolved by pull request %s\n [%s/%s]"
326+ % (pr_num , GITHUB_BASE , pr_num ))
345327 resolve_jira (title , merged_refs , jira_comment )
346328else :
347- print "Could not find jira-python library. Run 'sudo pip install jira-python' to install."
348- print "Exiting without trying to close the associated JIRA."
329+ print ("Could not find jira-python library. "
330+ "Run 'sudo pip install jira-python' to install." )
331+ print ("Exiting without trying to close the associated JIRA." )
0 commit comments