2626
2727import json
2828import os
29+ import re
2930import subprocess
3031import sys
3132import tempfile
3233import urllib2
3334
35+ try :
36+ import jira .client
37+ JIRA_IMPORTED = True
38+ except ImportError :
39+ JIRA_IMPORTED = False
40+
3441# Location of your Spark git development area
3542SPARK_HOME = os .environ .get ("SPARK_HOME" , "/home/patrick/Documents/spark" )
3643# Remote name which points to the Gihub site
3744PR_REMOTE_NAME = os .environ .get ("PR_REMOTE_NAME" , "apache-github" )
3845# Remote name which points to Apache git
3946PUSH_REMOTE_NAME = os .environ .get ("PUSH_REMOTE_NAME" , "apache" )
40-
41- GIT_API_BASE = "https://api.github.com/repos/apache/spark"
47+ # ASF JIRA username
48+ JIRA_USERNAME = os .environ .get ("JIRA_USERNAME" , "pwendell" )
49+ # ASF JIRA password
50+ JIRA_PASSWORD = os .environ .get ("JIRA_PASSWORD" , "1234" )
51+
52+ GITHUB_BASE = "https://github.com/apache/spark/pull"
53+ GITHUB_API_BASE = "https://api.github.com/repos/apache/spark"
54+ JIRA_BASE = "https://issues.apache.org/jira/browse"
55+ JIRA_API_BASE = "https://issues.apache.org/jira"
4256# Prefix added to temporary branches
4357BRANCH_PREFIX = "PR_TOOL"
4458
@@ -145,8 +159,7 @@ def merge_pr(pr_num, target_ref):
145159 return merge_hash
146160
147161
148- def maybe_cherry_pick (pr_num , merge_hash , default_branch ):
149- continue_maybe ("Would you like to pick %s into another branch?" % merge_hash )
162+ def cherry_pick (pr_num , merge_hash , default_branch ):
150163 pick_ref = raw_input ("Enter a branch name [%s]: " % default_branch )
151164 if pick_ref == "" :
152165 pick_ref = default_branch
@@ -171,14 +184,86 @@ def maybe_cherry_pick(pr_num, merge_hash, default_branch):
171184
172185 print ("Pull request #%s picked into %s!" % (pr_num , pick_ref ))
173186 print ("Pick hash: %s" % pick_hash )
187+ return pick_ref
188+
189+ def fix_version_from_branch (branch , versions ):
190+ # Note: Assumes this is a sorted (newest->oldest) list of un-released versions
191+ if branch == "master" :
192+ return versions [0 ]
193+ else :
194+ branch_ver = branch .replace ("branch-" , "" )
195+ return filter (lambda x : x .name .startswith (branch_ver ), versions )[- 1 ]
196+
197+ def resolve_jira (title , merge_branches , comment ):
198+ asf_jira = jira .client .JIRA ({'server' : JIRA_API_BASE },
199+ basic_auth = (JIRA_USERNAME , JIRA_PASSWORD ))
200+
201+ default_jira_id = ""
202+ search = re .findall ("SPARK-[0-9]{4,5}" , title )
203+ if len (search ) > 0 :
204+ default_jira_id = search [0 ]
205+
206+ jira_id = raw_input ("Enter a JIRA id [%s]: " % default_jira_id )
207+ if jira_id == "" :
208+ jira_id = default_jira_id
174209
175- branches = get_json ("%s/branches" % GIT_API_BASE )
210+ try :
211+ issue = asf_jira .issue (jira_id )
212+ except Exception as e :
213+ fail ("ASF JIRA could not find %s\n %s" % (jira_id , e ))
214+
215+ cur_status = issue .fields .status .name
216+ cur_summary = issue .fields .summary
217+ cur_assignee = issue .fields .assignee
218+ if cur_assignee == None :
219+ cur_assignee = "NOT ASSIGNED!!!"
220+ else :
221+ cur_assignee = cur_assignee .displayName
222+
223+ if cur_status == "Resolved" or cur_status == "Closed" :
224+ fail ("JIRA issue %s already has status '%s'" % (jira_id , cur_status ))
225+ print ("=== JIRA %s ===" % jira_id )
226+ print ("summary\t \t %s\n assignee\t %s\n status\t \t %s\n url\t \t %s/%s\n " % (
227+ cur_summary , cur_assignee , cur_status , JIRA_BASE , jira_id ))
228+
229+ versions = asf_jira .project_versions ("SPARK" )
230+ versions = sorted (versions , key = lambda x : x .name , reverse = True )
231+ versions = filter (lambda x : x .raw ['released' ] == False , versions )
232+
233+ default_fix_versions = map (lambda x : fix_version_from_branch (x , versions ).name , merge_branches )
234+ for v in default_fix_versions :
235+ # Handles the case where we have forked a release branch but not yet made the release.
236+ # In this case, if the PR is committed to the master branch and the release branch, we
237+ # only consider the release branch to be the fix version. E.g. it is not valid to have
238+ # both 1.1.0 and 1.0.0 as fix versions.
239+ (major , minor , patch ) = v .split ("." )
240+ if patch == 0 :
241+ previous = "%s.%s.%s" % (major , int (minor ) - 1 , 0 )
242+ if previous in default_fix_versions :
243+ default_fix_versions = filter (lambda x : x != v , default_fix_versions )
244+ default_fix_versions = "," .join (default_fix_versions )
245+
246+ fix_versions = raw_input ("Enter comma-separated fix version(s) [%s]: " % default_fix_versions )
247+ if fix_versions == "" :
248+ fix_versions = default_fix_versions
249+ fix_versions = fix_versions .replace (" " , "" ).split ("," )
250+
251+ def get_version_json (version_str ):
252+ return filter (lambda v : v .name == version_str , versions )[0 ].raw
253+ jira_fix_versions = map (lambda v : get_version_json (v ), fix_versions )
254+
255+ resolve = filter (lambda a : a ['name' ] == "Resolve Issue" , asf_jira .transitions (jira_id ))[0 ]
256+ asf_jira .transition_issue (jira_id , resolve ["id" ], fixVersions = jira_fix_versions , comment = comment )
257+
258+ print "Succesfully resolved %s with fixVersions=%s!" % (jira_id , fix_versions )
259+
260+ branches = get_json ("%s/branches" % GITHUB_API_BASE )
176261branch_names = filter (lambda x : x .startswith ("branch-" ), [x ['name' ] for x in branches ])
177262# Assumes branch names can be sorted lexicographically
178263latest_branch = sorted (branch_names , reverse = True )[0 ]
179264
180265pr_num = raw_input ("Which pull request would you like to merge? (e.g. 34): " )
181- pr = get_json ("%s/pulls/%s" % (GIT_API_BASE , pr_num ))
266+ pr = get_json ("%s/pulls/%s" % (GITHUB_API_BASE , pr_num ))
182267
183268url = pr ["url" ]
184269title = pr ["title" ]
@@ -208,11 +293,22 @@ def maybe_cherry_pick(pr_num, merge_hash, default_branch):
208293 continue_maybe (msg )
209294
210295print ("\n === Pull Request #%s ===" % pr_num )
211- print ("title\t %s\n source\t %s\n target\t %s\n url\t %s" % (
296+ print ("title\t %s\n source\t %s\n target\t %s\n url\t %s" % (
212297 title , pr_repo_desc , target_ref , url ))
213298continue_maybe ("Proceed with merging pull request #%s?" % pr_num )
214299
300+ merged_refs = [target_ref ]
301+
215302merge_hash = merge_pr (pr_num , target_ref )
216303
217- while True :
218- maybe_cherry_pick (pr_num , merge_hash , latest_branch )
304+ pick_prompt = "Would you like to pick %s into another branch?" % merge_hash
305+ while raw_input ("\n %s (y/n): " % pick_prompt ).lower () == "y" :
306+ merged_refs = merged_refs + [cherry_pick (pr_num , merge_hash , latest_branch )]
307+
308+ if JIRA_IMPORTED :
309+ continue_maybe ("Would you like to update an associated JIRA?" )
310+ jira_comment = "Issue resolved by pull request %s\n [%s/%s]" % (pr_num , GITHUB_BASE , pr_num )
311+ resolve_jira (title , merged_refs , jira_comment )
312+ else :
313+ print "Could not find jira-python library. Run 'sudo pip install jira-python' to install."
314+ print "Exiting without trying to close the associated JIRA."
0 commit comments