Improving Gerrit + commit-log automation.
I've reworked a few items here, so I will describe them in as much a linear fashion as possible. Keep in mind that one of the primary goals for these changes is to allow us to "trigger more magic from Gerrit". And it is to that end that I've implemented these changes. In find_bugs(), I've tightened-up the regular expression being used so that it will parse-out any prefixes associated with the bug reference. I've tested the regular expression against the most common bug references that I've seen in commit logs, as well as against the styles described in our documentation. The sources that I've drawn from are: https://etherpad.openstack.org/drive-automation-from-commitmsg https://wiki.openstack.org/wiki/GitCommitMessages https://wiki.openstack.org/wiki/Gerrit_Workflow Moreover, I'm using re.finditer() which allows for more direct access to the text that was matched. Lastly, I've tried to keep the expression as flexible as possible so that it will match even if the developer references the bug in a funky way. In order to keep the prefix and lp_task associated with each other, I've created a class called "Task" which is simply an interface to determine what sort of automation needs to take place for the given bugtask. This being the case, I've taken the liberty of renaming a few variables to make this more clear. In "Task", a basic level of processing is performed on the prefix to determine what changes need to be made on launchpad. A method called needs_change() returns a boolean indicating if the supplied argument is a change which needs to be made. Lastly, yet most importantly for this bug fix, process_bugtsk() is utilizing needs_change(), as mentioned above, to ensure that the bugtask's status is not erroneously changed in the case of a bug fix which spans multiple commits. Closes-Bug: 1018013 Change-Id: Ibd84d3c6edcf104afe3211fb55ea531efa92d20e
This commit is contained in:
parent
05a47939ff
commit
91c791a580
@ -230,29 +230,76 @@ def is_direct_release(full_project_name):
|
||||
]
|
||||
|
||||
|
||||
def process_bugtask(launchpad, bugtask, git_log, args):
|
||||
"""Apply changes to bugtask, based on hook / branch..."""
|
||||
class Task:
|
||||
def __init__(self, lp_task, prefix):
|
||||
'''Prefixes associated with bug references will allow for certain
|
||||
changes to be made to the bug's launchpad (lp) page. The following
|
||||
tokens represent the automation currently taking place.
|
||||
|
||||
::
|
||||
add_comment -> Adds a comment to the bug's lp page.
|
||||
set_in_progress -> Sets the bug's lp status to 'In Progress'.
|
||||
set_fix_released -> Sets the bug's lp status to 'Fix Released'.
|
||||
set_fix_committed -> Sets the bug's lp status to 'Fix Committed'.
|
||||
::
|
||||
|
||||
changes_needed, when populated, simply indicates the actions that are
|
||||
available to be taken based on the value of 'prefix'.
|
||||
'''
|
||||
self.lp_task = lp_task
|
||||
self.changes_needed = []
|
||||
|
||||
# If no prefix was matched, default to 'closes'.
|
||||
prefix = prefix.split('-')[0].lower() if prefix else 'closes'
|
||||
|
||||
if prefix in ('closes', 'fixes', 'resolves'):
|
||||
self.changes_needed.extend(('add_comment',
|
||||
'set_in_progress',
|
||||
'set_fix_committed',
|
||||
'set_fix_released'))
|
||||
elif prefix in ('partial',):
|
||||
self.changes_needed.extend(('add_comment', 'set_in_progress'))
|
||||
elif prefix in ('related', 'impacts', 'affects'):
|
||||
self.changes_needed.extend(('add_comment',))
|
||||
else:
|
||||
# prefix is not recognized.
|
||||
self.changes_needed.extend(('add_comment',))
|
||||
|
||||
def needs_change(self, change):
|
||||
'''Return a boolean indicating if given 'change' needs to be made.'''
|
||||
if change in self.changes_needed:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def process_bugtask(launchpad, task, git_log, args):
|
||||
"""Apply changes to lp bug tasks, based on hook / branch."""
|
||||
|
||||
bugtask = task.lp_task
|
||||
|
||||
if args.hook == "change-merged":
|
||||
if args.branch == 'master':
|
||||
if is_direct_release(args.project):
|
||||
if (is_direct_release(args.project) and
|
||||
task.needs_change('set_fix_released')):
|
||||
set_fix_released(bugtask)
|
||||
else:
|
||||
if bugtask.status != u'Fix Released':
|
||||
if (bugtask.status != u'Fix Released' and
|
||||
task.needs_change('set_fix_committed')):
|
||||
set_fix_committed(bugtask)
|
||||
elif args.branch == 'milestone-proposed':
|
||||
release_fixcommitted(bugtask)
|
||||
elif args.branch.startswith('stable/'):
|
||||
series = args.branch[7:]
|
||||
# Look for a related task matching the series
|
||||
# Look for a related task matching the series.
|
||||
for reltask in bugtask.related_tasks:
|
||||
if (reltask.bug_target_name.endswith("/" + series) and
|
||||
reltask.status != u'Fix Released'):
|
||||
# Use fixcommitted if there is any
|
||||
reltask.status != u'Fix Released' and
|
||||
task.needs_change('set_fix_committed')):
|
||||
set_fix_committed(reltask)
|
||||
break
|
||||
else:
|
||||
# Use tagging if there isn't any
|
||||
# Use tag_in_branchname if there isn't any.
|
||||
tag_in_branchname(bugtask, args.branch)
|
||||
|
||||
add_change_merged_message(bugtask, args.change_url, args.project,
|
||||
@ -261,13 +308,15 @@ def process_bugtask(launchpad, bugtask, git_log, args):
|
||||
|
||||
if args.hook == "patchset-created":
|
||||
if args.branch == 'master':
|
||||
if bugtask.status not in [u'Fix Committed', u'Fix Released']:
|
||||
set_in_progress(bugtask, launchpad, args.uploader,
|
||||
args.change_url)
|
||||
if (bugtask.status not in [u'Fix Committed', u'Fix Released'] and
|
||||
task.needs_change('set_in_progress')):
|
||||
set_in_progress(bugtask, launchpad,
|
||||
args.uploader, args.change_url)
|
||||
elif args.branch.startswith('stable/'):
|
||||
series = args.branch[7:]
|
||||
for reltask in bugtask.related_tasks:
|
||||
if (reltask.bug_target_name.endswith("/" + series) and
|
||||
task.needs_change('set_in_progress') and
|
||||
reltask.status not in [u'Fix Committed',
|
||||
u'Fix Released']):
|
||||
set_in_progress(reltask, launchpad,
|
||||
@ -280,23 +329,44 @@ def process_bugtask(launchpad, bugtask, git_log, args):
|
||||
|
||||
|
||||
def find_bugs(launchpad, git_log, args):
|
||||
"""Find bugs referenced in the git log and return related bugtasks."""
|
||||
'''Find bugs referenced in the git log and return related tasks.
|
||||
|
||||
bug_regexp = r'([Bb]ug|[Ll][Pp])[\s#:]*(\d+)'
|
||||
tokens = re.split(bug_regexp, git_log)
|
||||
Our regular expression is composed of three major parts:
|
||||
part1: Matches only at start-of-line (required). Optionally matches any
|
||||
word or hyphen separated words.
|
||||
part2: Matches the words 'bug' or 'lp' on a word boundry (required).
|
||||
part3: Matches a whole number (required).
|
||||
|
||||
# Extract unique bug tasks
|
||||
The following patterns will be matched properly:
|
||||
bug # 555555
|
||||
Closes-Bug: 555555
|
||||
Fixes: bug # 555555
|
||||
Resolves: bug 555555
|
||||
Partial-Bug: lp bug # 555555
|
||||
|
||||
:returns: an iterable containing Task objects.
|
||||
'''
|
||||
|
||||
part1 = r'^[\t ]*(?P<prefix>[-\w]+)?[\s:]*'
|
||||
part2 = r'(?:\b(?:bug|lp)\b[\s#:]*)+'
|
||||
part3 = r'(?P<bug_number>\d+)\s*?$'
|
||||
regexp = part1 + part2 + part3
|
||||
matches = re.finditer(regexp, git_log, flags=re.I | re.M)
|
||||
|
||||
# Extract unique bug tasks and associated prefixes.
|
||||
bugtasks = {}
|
||||
for token in tokens:
|
||||
if re.match('^\d+$', token) and (token not in bugtasks):
|
||||
for match in matches:
|
||||
prefix = match.group('prefix')
|
||||
bug_num = match.group('bug_number')
|
||||
if bug_num not in bugtasks:
|
||||
try:
|
||||
lp_bug = launchpad.bugs[token]
|
||||
lp_bug = launchpad.bugs[bug_num]
|
||||
for lp_task in lp_bug.bug_tasks:
|
||||
if lp_task.bug_target_name == git2lp(args.project):
|
||||
bugtasks[token] = lp_task
|
||||
bugtasks[bug_num] = Task(lp_task, prefix)
|
||||
break
|
||||
except KeyError:
|
||||
# Unknown bug
|
||||
# Unknown bug.
|
||||
pass
|
||||
|
||||
return bugtasks.values()
|
||||
@ -313,31 +383,31 @@ def extract_git_log(args):
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('hook')
|
||||
#common
|
||||
# common
|
||||
parser.add_argument('--change', default=None)
|
||||
parser.add_argument('--change-url', default=None)
|
||||
parser.add_argument('--project', default=None)
|
||||
parser.add_argument('--branch', default=None)
|
||||
parser.add_argument('--commit', default=None)
|
||||
#change-merged
|
||||
# change-merged
|
||||
parser.add_argument('--submitter', default=None)
|
||||
#patchset-created
|
||||
# patchset-created
|
||||
parser.add_argument('--uploader', default=None)
|
||||
parser.add_argument('--patchset', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Connect to Launchpad
|
||||
# Connect to Launchpad.
|
||||
lpconn = launchpad.Launchpad.login_with(
|
||||
'Gerrit User Sync', uris.LPNET_SERVICE_ROOT, GERRIT_CACHE_DIR,
|
||||
credentials_file=GERRIT_CREDENTIALS, version='devel')
|
||||
|
||||
# Get git log
|
||||
# Get git log.
|
||||
git_log = extract_git_log(args)
|
||||
|
||||
# Process bugtasks found in git log
|
||||
for bugtask in find_bugs(lpconn, git_log, args):
|
||||
process_bugtask(lpconn, bugtask, git_log, args)
|
||||
# Process tasks found in git log.
|
||||
for task in find_bugs(lpconn, git_log, args):
|
||||
process_bugtask(lpconn, task, git_log, args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user