]>
git.ipfire.org Git - thirdparty/gcc.git/blob - maintainer-scripts/branch_changer.py
3 # This script is used by maintainers to modify Bugzilla entries in batch
5 # Currently it can remove and add a release from/to PRs that are prefixed
6 # with '[x Regression]'. Apart from that, it can also change target
7 # milestones and optionally enhance the list of known-to-fail versions.
9 # The script utilizes the Bugzilla API, as documented here:
10 # http://bugzilla.readthedocs.io/en/latest/api/index.html
12 # It requires the simplejson, requests, semantic_version packages.
13 # In case of openSUSE:
14 # zypper in python3-simplejson python3-requests
15 # pip3 install semantic_version
17 # Sample usages of the script:
19 # $ ./maintainer-scripts/branch_changer.py api_key --new-target-milestone=6.2:6.3 --comment '6.2 has been released....' --add-known-to-fail=6.2 --limit 3
21 # The invocation will set target milestone to 6.3 for all issues that
22 # have mistone equal to 6.2. Apart from that, a comment is added to these
23 # issues and 6.2 version is added to known-to-fail versions.
24 # At maximum 3 issues will be modified and the script will run
25 # in dry mode (no issues are modified), unless you append --doit option.
27 # $ ./maintainer-scripts/branch_changer.py api_key --new-target-milestone=5.5:6.3 --comment 'GCC 5 branch is being closed' --remove 5 --limit 3
29 # Very similar to previous invocation, but instead of adding to known-to-fail,
30 # '5' release is removed from all issues that have the regression prefix.
32 # $ ./maintainer-scripts/branch_changer.py api_key --add=7:8
34 # Aforementioned invocation adds '8' release to the regression prefix of all
35 # issues that contain '7' in its regression prefix.
43 from semantic_version
import Version
45 base_url
= 'https://gcc.gnu.org/bugzilla/rest.cgi/'
46 statuses
= [ 'UNCONFIRMED' , 'ASSIGNED' , 'SUSPENDED' , 'NEW' , 'WAITING' , 'REOPENED' ]
47 search_summary
= ' Regression]'
48 regex
= '(.*\[)([0-9\./]*)( [rR]egression])(.*)'
51 def __init__ ( self
, data
):
54 self
. fail_versions
= []
55 self
. is_regression
= False
58 self
. parse_known_to_fail ()
60 def parse_summary ( self
):
61 m
= re
. match ( regex
, self
. data
[ 'summary' ])
63 self
. versions
= m
. group ( 2 ). split ( '/' )
64 self
. is_regression
= True
67 def parse_known_to_fail ( self
):
68 v
= self
. data
[ 'cf_known_to_fail' ]. strip ()
70 self
. fail_versions
= [ x
for x
in re
. split ( ' |,' , v
) if x
!= '' ]
73 return 'PR %d ( %s )' % ( self
. data
[ 'id' ], self
. data
[ 'summary' ])
75 def remove_release ( self
, release
):
76 # Do not remove last value of [x Regression]
77 if len ( self
. versions
) == 1 :
79 self
. versions
= list ( filter ( lambda x
: x
!= release
, self
. versions
))
81 def add_release ( self
, releases
):
82 parts
= releases
. split ( ':' )
83 assert len ( parts
) == 2
84 for i
, v
in enumerate ( self
. versions
):
86 self
. versions
. insert ( i
+ 1 , parts
[ 1 ])
89 def add_known_to_fail ( self
, release
):
90 if release
in self
. fail_versions
:
93 self
. fail_versions
. append ( release
)
96 def update_summary ( self
, api_key
, doit
):
97 summary
= self
. data
[ 'summary' ]
98 new_summary
= self
. serialize_summary ()
99 if new_summary
!= summary
:
101 print ( ' changing summary: " %s " to " %s "' % ( summary
, new_summary
))
102 self
. modify_bug ( api_key
, { 'summary' : new_summary
}, doit
)
108 def change_milestone ( self
, api_key
, old_milestone
, new_milestone
, comment
, new_fail_version
, doit
):
109 old_major
= Bug
. get_major_version ( old_milestone
)
110 new_major
= Bug
. get_major_version ( new_milestone
)
114 if old_major
== new_major
:
115 args
[ 'target_milestone' ] = new_milestone
116 print ( ' changing target milestone: " %s " to " %s " (same branch)' % ( old_milestone
, new_milestone
))
117 elif self
. is_regression
and new_major
in self
. versions
:
118 args
[ 'target_milestone' ] = new_milestone
119 print ( ' changing target milestone: " %s " to " %s " (regresses with the new milestone)' % ( old_milestone
, new_milestone
))
121 print ( ' not changing target milestone: not a regression or does not regress with the new milestone' )
123 if 'target_milestone' in args
and comment
!= None :
124 print ( ' adding comment: " %s "' % comment
)
125 args
[ 'comment' ] = { 'comment' : comment
}
127 if new_fail_version
!= None :
128 if self
. add_known_to_fail ( new_fail_version
):
129 s
= self
. serialize_known_to_fail ()
130 print ( ' changing known_to_fail: " %s " to " %s "' % ( self
. data
[ 'cf_known_to_fail' ], s
))
131 args
[ 'cf_known_to_fail' ] = s
133 if len ( args
. keys ()) != 0 :
134 self
. modify_bug ( api_key
, args
, doit
)
139 def serialize_summary ( self
):
140 assert self
. versions
!= None
141 assert self
. is_regression
== True
143 new_version
= '/' . join ( self
. versions
)
144 new_summary
= self
. regex_match
. group ( 1 ) + new_version
+ self
. regex_match
. group ( 3 ) + self
. regex_match
. group ( 4 )
147 def serialize_known_to_fail ( self
):
148 assert type ( self
. fail_versions
) is list
149 return ', ' . join ( sorted ( self
. fail_versions
, key
= lambda x
: Version ( x
, partial
= True )))
151 def modify_bug ( self
, api_key
, params
, doit
):
152 u
= base_url
+ 'bug/' + str ( self
. data
[ 'id' ])
155 'ids' : [ self
. data
[ 'id' ]],
161 r
= requests
. put ( u
, data
= json
. dumps ( data
), headers
= { "content-type" : "text/javascript" })
165 def get_major_version ( release
):
166 parts
= release
. split ( '.' )
167 assert len ( parts
) == 2 or len ( parts
) == 3
168 return '.' . join ( parts
[:- 1 ])
171 def get_bugs ( api_key
, query
):
173 r
= requests
. get ( u
, params
= query
)
174 return [ Bug ( x
) for x
in r
. json ()[ 'bugs' ]]
176 def search ( api_key
, remove
, add
, limit
, doit
):
177 bugs
= Bug
. get_bugs ( api_key
, { 'api_key' : api_key
, 'summary' : search_summary
, 'bug_status' : statuses
})
178 bugs
= list ( filter ( lambda x
: x
. is_regression
, bugs
))
183 bug
. remove_release ( remove
)
187 if bug
. update_summary ( api_key
, doit
):
189 if modified
== limit
:
192 print ( ' \n Modified PRs: %d ' % modified
)
194 def replace_milestone ( api_key
, limit
, old_milestone
, new_milestone
, comment
, add_known_to_fail
, doit
):
195 bugs
= Bug
. get_bugs ( api_key
, { 'api_key' : api_key
, 'bug_status' : statuses
, 'target_milestone' : old_milestone
})
199 if bug
. change_milestone ( api_key
, old_milestone
, new_milestone
, comment
, add_known_to_fail
, doit
):
201 if modified
== limit
:
204 print ( ' \n Modified PRs: %d ' % modified
)
206 parser
= argparse
. ArgumentParser ( description
= '' )
207 parser
. add_argument ( 'api_key' , help = 'API key' )
208 parser
. add_argument ( '--remove' , nargs
= '?' , help = 'Remove a release from summary' )
209 parser
. add_argument ( '--add' , nargs
= '?' , help = 'Add a new release to summary, e.g. 6:7 will add 7 where 6 is included' )
210 parser
. add_argument ( '--limit' , nargs
= '?' , help = 'Limit number of bugs affected by the script' )
211 parser
. add_argument ( '--doit' , action
= 'store_true' , help = 'Really modify BUGs in the bugzilla' )
212 parser
. add_argument ( '--new-target-milestone' , help = 'Set a new target milestone, e.g. 4.9.3:4.9.4 will set milestone to 4.9.4 for all PRs having milestone set to 4.9.3' )
213 parser
. add_argument ( '--add-known-to-fail' , help = 'Set a new known to fail for all PRs affected by --new-target-milestone' )
214 parser
. add_argument ( '--comment' , help = 'Comment a PR for which we set a new target milestore' )
216 args
= parser
. parse_args ()
217 # Python3 does not have sys.maxint
218 args
. limit
= int ( args
. limit
) if args
. limit
!= None else 10 ** 10
220 if args
. remove
!= None or args
. add
!= None :
221 search ( args
. api_key
, args
. remove
, args
. add
, args
. limit
, args
. doit
)
222 if args
. new_target_milestone
!= None :
223 t
= args
. new_target_milestone
. split ( ':' )
225 replace_milestone ( args
. api_key
, args
. limit
, t
[ 0 ], t
[ 1 ], args
. comment
, args
. add_known_to_fail
, args
. doit
)