#!/usr/bin/env python # Copyright (c) 2011 OpenStack, LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is designed to be called by a gerrit hook. It searched new # patchsets for strings like "bug FOO" and updates corresponding Launchpad # bugs status. from launchpadlib.launchpad import Launchpad from launchpadlib.uris import LPNET_SERVICE_ROOT import os import argparse import re import subprocess BASE_DIR = '/home/gerrit2/review_site' GERRIT_CACHE_DIR = os.path.expanduser(os.environ.get('GERRIT_CACHE_DIR', '~/.launchpadlib/cache')) GERRIT_CREDENTIALS = os.path.expanduser(os.environ.get('GERRIT_CREDENTIALS', '~/.launchpadlib/creds')) def add_change_proposed_message(bugtask, change_url, project, branch): subject = 'Fix proposed to %s (%s)' % (short_project(project), branch) body = 'Fix proposed to branch: %s\nReview: %s' % (branch, change_url) bugtask.bug.newMessage(subject=subject, content=body) def add_change_merged_message(bugtask, change_url, project, commit, submitter, branch, git_log): subject = 'Fix merged to %s (%s)' % (short_project(project), branch) git_url = 'http://github.com/%s/commit/%s' % (project, commit) body = '''Reviewed: %s Committed: %s Submitter: %s Branch: %s\n''' % (change_url, git_url, submitter, branch) body = body + '\n' + git_log bugtask.bug.newMessage(subject=subject, content=body) def set_in_progress(bugtask, launchpad, uploader, change_url): """Set bug In progress with assignee being the uploader""" # Retrieve uploader from Launchpad. Use email as search key if # provided, and only set if there is a clear match. try: searchkey = uploader[uploader.rindex("(") + 1:-1] except ValueError: searchkey = uploader persons = launchpad.people.findPerson(text=searchkey) if len(persons) == 1: bugtask.assignee = persons[0] bugtask.status = "In Progress" bugtask.lp_save() def set_fix_committed(bugtask): """Set bug fix committed""" bugtask.status = "Fix Committed" bugtask.lp_save() def set_fix_released(bugtask): """Set bug fix released""" bugtask.status = "Fix Released" bugtask.lp_save() def release_fixcommitted(bugtask): """Set bug FixReleased if it was FixCommitted""" if bugtask.status == u'Fix Committed': set_fix_released(bugtask) def tag_in_branchname(bugtask, branch): """Tag bug with in-branch-name tag (if name is appropriate)""" lp_bug = bugtask.bug branch_name = branch.replace('/', '-') if branch_name.replace('-', '').isalnum(): lp_bug.tags = lp_bug.tags + ["in-%s" % branch_name] lp_bug.tags.append("in-%s" % branch_name) lp_bug.lp_save() def short_project(full_project_name): """Return the project part of the git repository name""" return full_project_name.split('/')[-1] def git2lp(full_project_name): """Convert Git repo name to Launchpad project""" project_map = { 'openstack/openstack-ci-puppet': 'openstack-ci', 'openstack-ci/devstack-gate': 'openstack-ci', 'openstack-ci/gerrit': 'openstack-ci', 'openstack-ci/lodgeit': 'openstack-ci', 'openstack-ci/meetbot': 'openstack-ci', } return project_map.get(full_project_name, short_project(full_project_name)) def is_direct_release(full_project_name): """Test against a list of projects who directly release changes.""" return full_project_name in [ 'openstack-ci/devstack-gate', 'openstack-ci/lodgeit', 'openstack-ci/meetbot', 'openstack-dev/devstack', 'openstack/openstack-ci', 'openstack/openstack-ci-puppet', 'openstack/openstack-manuals', 'openstack/tempest', ] def process_bugtask(launchpad, bugtask, git_log, args): """Apply changes to bugtask, based on hook / branch...""" if args.hook == "change-merged": if args.branch == 'master': if is_direct_release(args.project): set_fix_released(bugtask) else: 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 for reltask in bugtask.related_tasks: if reltask.bug_target_name.endswith("/" + series): # Use fixcommitted if there is any set_fix_committed(reltask) break else: # Use tagging if there isn't any tag_in_branchname(bugtask, args.branch) add_change_merged_message(bugtask, args.change_url, args.project, args.commit, args.submitter, args.branch, git_log) if args.hook == "patchset-created": if args.branch == 'master': 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): set_in_progress(reltask, launchpad, args.uploader, args.change_url) break if args.patchset == '1': add_change_proposed_message(bugtask, args.change_url, args.project, args.branch) def find_bugs(launchpad, git_log, args): """Find bugs referenced in the git log and return related bugtasks""" bug_regexp = r'([Bb]ug|[Ll][Pp])[\s#:]*(\d+)' tokens = re.split(bug_regexp, git_log) # Extract unique bug tasks bugtasks = {} for token in tokens: if re.match('^\d+$', token) and (token not in bugtasks): try: lp_bug = launchpad.bugs[token] for lp_task in lp_bug.bug_tasks: if lp_task.bug_target_name == git2lp(args.project): bugtasks[token] = lp_task break except KeyError: # Unknown bug pass return bugtasks.values() def extract_git_log(args): """Extract git log of all merged commits""" cmd = ['git', '--git-dir=' + BASE_DIR + '/git/' + args.project + '.git', 'log', '--no-merges', args.commit + '^1..' + args.commit] return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] def main(): parser = argparse.ArgumentParser() parser.add_argument('hook') #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 parser.add_argument('--submitter', default=None) #patchset-created parser.add_argument('--uploader', default=None) parser.add_argument('--patchset', default=None) args = parser.parse_args() # Connect to Launchpad launchpad = Launchpad.login_with('Gerrit User Sync', LPNET_SERVICE_ROOT, GERRIT_CACHE_DIR, credentials_file=GERRIT_CREDENTIALS, version='devel') # Get git log git_log = extract_git_log(args) # Process bugtasks found in git log for bugtask in find_bugs(launchpad, git_log, args): process_bugtask(launchpad, bugtask, git_log, args) if __name__ == '__main__': main()