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 )
@@ -195,21 +199,24 @@ def merge_pr(pr_num, target_ref):
195199
196200
197201def cherry_pick (pr_num , merge_hash , default_branch ):
198- pick_ref = raw_input ("Enter a branch name [%s]: " % default_branch )
202+ pick_ref = input ("Enter a branch name [%s]: " % default_branch )
199203 if pick_ref == "" :
200204 pick_ref = default_branch
201205
202- pick_branch_name = "%s_PICK_PR_%s_%s" % (BRANCH_PREFIX , pr_num , pick_ref .upper ())
206+ pick_branch_name = "%s_PICK_PR_%s_%s" % (BRANCH_PREFIX , pr_num ,
207+ pick_ref .upper ())
203208
204- run_cmd ("git fetch %s %s:%s" % (PUSH_REMOTE_NAME , pick_ref , pick_branch_name ))
209+ run_cmd ("git fetch %s %s:%s" % (PUSH_REMOTE_NAME , pick_ref ,
210+ pick_branch_name ))
205211 run_cmd ("git checkout %s" % pick_branch_name )
206212 run_cmd ("git cherry-pick -sx %s" % merge_hash )
207213
208214 continue_maybe ("Pick complete (local ref %s). Push to %s?" % (
209215 pick_branch_name , PUSH_REMOTE_NAME ))
210216
211217 try :
212- run_cmd ('git push %s %s:%s' % (PUSH_REMOTE_NAME , pick_branch_name , pick_ref ))
218+ run_cmd ('git push %s %s:%s' % (PUSH_REMOTE_NAME , pick_branch_name ,
219+ pick_ref ))
213220 except Exception as e :
214221 clean_up ()
215222 fail ("Exception while pushing: %s" % e )
@@ -223,36 +230,41 @@ def cherry_pick(pr_num, merge_hash, default_branch):
223230
224231
225232def fix_version_from_branch (branch , versions ):
226- # Note: Assumes this is a sorted (newest->oldest) list of un-released versions
233+ # Note: Assumes this is a sorted (newest->oldest) list of un-released
234+ # versions
227235 if branch == "master" :
228236 return versions [0 ]
229237 else :
230238 branch_ver = branch .replace ("branch-" , "" )
231- return filter (lambda x : x .name .startswith (branch_ver ), versions )[- 1 ]
239+ return [x for x in versions if x .name .startswith (branch_ver )][- 1 ]
240+
232241
233242def exctract_jira_id (title ):
234243 m = re .search (r'^(ARROW-[0-9]+)\b.*$' , title )
235244 if m and m .groups > 0 :
236245 return m .group (1 )
237246 else :
238- fail ("PR title should be prefixed by a jira id \" ARROW-XXX: ...\" , found: \" %s\" " % title )
247+ fail ("PR title should be prefixed by a jira id "
248+ "\" ARROW-XXX: ...\" , found: \" %s\" " % title )
249+
239250
240251def check_jira (title ):
241252 jira_id = exctract_jira_id (title )
242253 asf_jira = jira .client .JIRA ({'server' : JIRA_API_BASE },
243254 basic_auth = (JIRA_USERNAME , JIRA_PASSWORD ))
244255 try :
245- issue = asf_jira .issue (jira_id )
256+ asf_jira .issue (jira_id )
246257 except Exception as e :
247258 fail ("ASF JIRA could not find %s\n %s" % (jira_id , e ))
248259
260+
249261def resolve_jira (title , merge_branches , comment ):
250262 asf_jira = jira .client .JIRA ({'server' : JIRA_API_BASE },
251263 basic_auth = (JIRA_USERNAME , JIRA_PASSWORD ))
252264
253265 default_jira_id = exctract_jira_id (title )
254266
255- jira_id = raw_input ("Enter a JIRA id [%s]: " % default_jira_id )
267+ jira_id = input ("Enter a JIRA id [%s]: " % default_jira_id )
256268 if jira_id == "" :
257269 jira_id = default_jira_id
258270
@@ -271,30 +283,33 @@ def resolve_jira(title, merge_branches, comment):
271283
272284 if cur_status == "Resolved" or cur_status == "Closed" :
273285 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 ))
286+ print ("=== JIRA %s ===" % jira_id )
287+ print ("summary\t \t %s\n assignee\t %s\n status\t \t %s\n url\t \t %s/%sf \n "
288+ % ( cur_summary , cur_assignee , cur_status , JIRA_BASE , jira_id ))
277289
278290 resolve = filter (lambda a : a ['name' ] == "Resolve Issue" ,
279291 asf_jira .transitions (jira_id ))[0 ]
280292 asf_jira .transition_issue (jira_id , resolve ["id" ], comment = comment )
281293
282- print "Succesfully resolved %s!" % (jira_id )
294+ print ( "Succesfully resolved %s!" % (jira_id ) )
283295
284296
285297if not JIRA_USERNAME :
286- JIRA_USERNAME = raw_input ("Env JIRA_USERNAME not set, please enter your JIRA username:" )
298+ JIRA_USERNAME = input ("Env JIRA_USERNAME not set, "
299+ "please enter your JIRA username:" )
287300
288301if not JIRA_PASSWORD :
289- JIRA_PASSWORD = getpass .getpass ("Env JIRA_PASSWORD not set, please enter your JIRA password:" )
302+ JIRA_PASSWORD = getpass .getpass ("Env JIRA_PASSWORD not set, please enter "
303+ "your JIRA password:" )
290304
291305branches = get_json ("%s/branches" % GITHUB_API_BASE )
292- branch_names = filter (lambda x : x .startswith ("branch-" ), [x ['name' ] for x in branches ])
306+ branch_names = [x ['name' ] for x in branches if x ['name' ].startswith ('branch-' )]
307+
293308# Assumes branch names can be sorted lexicographically
294309# Julien: I commented this out as we don't have any "branch-*" branch yet
295- #latest_branch = sorted(branch_names, reverse=True)[0]
310+ # latest_branch = sorted(branch_names, reverse=True)[0]
296311
297- pr_num = raw_input ("Which pull request would you like to merge? (e.g. 34): " )
312+ pr_num = input ("Which pull request would you like to merge? (e.g. 34): " )
298313pr = get_json ("%s/pulls/%s" % (GITHUB_API_BASE , pr_num ))
299314
300315url = pr ["url" ]
@@ -307,42 +322,47 @@ def resolve_jira(title, merge_branches, comment):
307322pr_repo_desc = "%s/%s" % (user_login , base_ref )
308323
309324if pr ["merged" ] is True :
310- print "Pull request %s has already been merged, assuming you want to backport" % pr_num
325+ print ("Pull request %s has already been merged, "
326+ "assuming you want to backport" % pr_num )
311327 merge_commit_desc = run_cmd ([
312328 'git' , 'log' , '--merges' , '--first-parent' ,
313329 '--grep=pull request #%s' % pr_num , '--oneline' ]).split ("\n " )[0 ]
314330 if merge_commit_desc == "" :
315- fail ("Couldn't find any merge commit for #%s, you may need to update HEAD." % pr_num )
331+ fail ("Couldn't find any merge commit for #%s, "
332+ "you may need to update HEAD." % pr_num )
316333
317334 merge_hash = merge_commit_desc [:7 ]
318335 message = merge_commit_desc [8 :]
319336
320- print "Found: %s" % message
321- maybe_cherry_pick (pr_num , merge_hash , latest_branch )
337+ print ( "Found: %s" % message )
338+ # maybe_cherry_pick(pr_num, merge_hash, latest_branch)
322339 sys .exit (0 )
323340
324341if not bool (pr ["mergeable" ]):
325- msg = "Pull request %s is not mergeable in its current form.\n " % pr_num + \
326- "Continue? (experts only!)"
342+ msg = ( "Pull request %s is not mergeable in its current form.\n "
343+ % pr_num + "Continue? (experts only!)" )
327344 continue_maybe (msg )
328345
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 ))
346+ print ("\n === Pull Request #%s ===" % pr_num )
347+ print ("title\t %s\n source\t %s\n target\t %s\n url\t %s"
348+ % ( title , pr_repo_desc , target_ref , url ))
332349continue_maybe ("Proceed with merging pull request #%s?" % pr_num )
333350
334351merged_refs = [target_ref ]
335352
336353merge_hash = merge_pr (pr_num , target_ref )
337354
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 )]
355+ # pick_prompt = "Would you like to pick %s into another branch?" % merge_hash
356+ # while input("\n%s (y/n): " % pick_prompt).lower() == "y":
357+ # merged_refs = merged_refs + [cherry_pick(pr_num, merge_hash,
358+ # latest_branch)]
341359
342360if JIRA_IMPORTED :
343361 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 )
362+ jira_comment = ("Issue resolved by pull request %s\n [%s/%s]"
363+ % (pr_num , GITHUB_BASE , pr_num ))
345364 resolve_jira (title , merged_refs , jira_comment )
346365else :
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."
366+ print ("Could not find jira-python library. "
367+ "Run 'sudo pip install jira-python' to install." )
368+ print ("Exiting without trying to close the associated JIRA." )
0 commit comments