Monty Taylor | 6c9634c | 2012-07-28 11:27:47 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2011 OpenStack, LLC. |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 13 | # License for the specific language governing permissions and limitations |
| 14 | # under the License. |
| 15 | |
| 16 | # This is designed to be called by a gerrit hook. It searched new |
| 17 | # patchsets for strings like "bug FOO" and updates corresponding Launchpad |
| 18 | # bugs status. |
| 19 | |
| 20 | from launchpadlib.launchpad import Launchpad |
| 21 | from launchpadlib.uris import LPNET_SERVICE_ROOT |
| 22 | import os |
| 23 | import argparse |
| 24 | import re |
| 25 | import subprocess |
| 26 | |
| 27 | |
| 28 | BASE_DIR = '/home/gerrit2/review_site' |
Monty Taylor | da3bada | 2012-11-22 09:38:22 -0800 | [diff] [blame^] | 29 | GERRIT_CACHE_DIR = os.path.expanduser( |
| 30 | os.environ.get('GERRIT_CACHE_DIR', |
| 31 | '~/.launchpadlib/cache')) |
| 32 | GERRIT_CREDENTIALS = os.path.expanduser( |
| 33 | os.environ.get('GERRIT_CREDENTIALS', |
| 34 | '~/.launchpadlib/creds')) |
Monty Taylor | 6c9634c | 2012-07-28 11:27:47 -0500 | [diff] [blame] | 35 | |
| 36 | |
| 37 | def add_change_proposed_message(bugtask, change_url, project, branch): |
| 38 | subject = 'Fix proposed to %s (%s)' % (short_project(project), branch) |
| 39 | body = 'Fix proposed to branch: %s\nReview: %s' % (branch, change_url) |
| 40 | bugtask.bug.newMessage(subject=subject, content=body) |
| 41 | |
| 42 | |
| 43 | def add_change_merged_message(bugtask, change_url, project, commit, |
| 44 | submitter, branch, git_log): |
| 45 | subject = 'Fix merged to %s (%s)' % (short_project(project), branch) |
| 46 | git_url = 'http://github.com/%s/commit/%s' % (project, commit) |
| 47 | body = '''Reviewed: %s |
| 48 | Committed: %s |
| 49 | Submitter: %s |
| 50 | Branch: %s\n''' % (change_url, git_url, submitter, branch) |
| 51 | body = body + '\n' + git_log |
| 52 | bugtask.bug.newMessage(subject=subject, content=body) |
| 53 | |
| 54 | |
| 55 | def set_in_progress(bugtask, launchpad, uploader, change_url): |
| 56 | """Set bug In progress with assignee being the uploader""" |
| 57 | |
| 58 | # Retrieve uploader from Launchpad. Use email as search key if |
| 59 | # provided, and only set if there is a clear match. |
| 60 | try: |
| 61 | searchkey = uploader[uploader.rindex("(") + 1:-1] |
| 62 | except ValueError: |
| 63 | searchkey = uploader |
| 64 | persons = launchpad.people.findPerson(text=searchkey) |
| 65 | if len(persons) == 1: |
| 66 | bugtask.assignee = persons[0] |
| 67 | |
| 68 | bugtask.status = "In Progress" |
| 69 | bugtask.lp_save() |
| 70 | |
| 71 | |
| 72 | def set_fix_committed(bugtask): |
| 73 | """Set bug fix committed""" |
| 74 | |
| 75 | bugtask.status = "Fix Committed" |
| 76 | bugtask.lp_save() |
| 77 | |
| 78 | |
| 79 | def set_fix_released(bugtask): |
| 80 | """Set bug fix released""" |
| 81 | |
| 82 | bugtask.status = "Fix Released" |
| 83 | bugtask.lp_save() |
| 84 | |
| 85 | |
| 86 | def release_fixcommitted(bugtask): |
| 87 | """Set bug FixReleased if it was FixCommitted""" |
| 88 | |
| 89 | if bugtask.status == u'Fix Committed': |
| 90 | set_fix_released(bugtask) |
| 91 | |
| 92 | |
| 93 | def tag_in_branchname(bugtask, branch): |
| 94 | """Tag bug with in-branch-name tag (if name is appropriate)""" |
| 95 | |
| 96 | lp_bug = bugtask.bug |
| 97 | branch_name = branch.replace('/', '-') |
| 98 | if branch_name.replace('-', '').isalnum(): |
| 99 | lp_bug.tags = lp_bug.tags + ["in-%s" % branch_name] |
| 100 | lp_bug.tags.append("in-%s" % branch_name) |
| 101 | lp_bug.lp_save() |
| 102 | |
| 103 | |
| 104 | def short_project(full_project_name): |
| 105 | """Return the project part of the git repository name""" |
| 106 | return full_project_name.split('/')[-1] |
| 107 | |
| 108 | |
| 109 | def git2lp(full_project_name): |
| 110 | """Convert Git repo name to Launchpad project""" |
| 111 | project_map = { |
| 112 | 'openstack/openstack-ci-puppet': 'openstack-ci', |
| 113 | 'openstack-ci/devstack-gate': 'openstack-ci', |
| 114 | 'openstack-ci/gerrit': 'openstack-ci', |
| 115 | 'openstack-ci/lodgeit': 'openstack-ci', |
| 116 | 'openstack-ci/meetbot': 'openstack-ci', |
Monty Taylor | da3bada | 2012-11-22 09:38:22 -0800 | [diff] [blame^] | 117 | } |
Monty Taylor | 6c9634c | 2012-07-28 11:27:47 -0500 | [diff] [blame] | 118 | return project_map.get(full_project_name, short_project(full_project_name)) |
| 119 | |
| 120 | |
| 121 | def is_direct_release(full_project_name): |
| 122 | """Test against a list of projects who directly release changes.""" |
| 123 | return full_project_name in [ |
| 124 | 'openstack-ci/devstack-gate', |
| 125 | 'openstack-ci/lodgeit', |
| 126 | 'openstack-ci/meetbot', |
| 127 | 'openstack-dev/devstack', |
| 128 | 'openstack/openstack-ci', |
| 129 | 'openstack/openstack-ci-puppet', |
| 130 | 'openstack/openstack-manuals', |
Thierry Carrez | 101d17e | 2012-09-28 11:54:38 +0200 | [diff] [blame] | 131 | 'openstack/tempest', |
Monty Taylor | da3bada | 2012-11-22 09:38:22 -0800 | [diff] [blame^] | 132 | ] |
Monty Taylor | 6c9634c | 2012-07-28 11:27:47 -0500 | [diff] [blame] | 133 | |
| 134 | |
| 135 | def process_bugtask(launchpad, bugtask, git_log, args): |
| 136 | """Apply changes to bugtask, based on hook / branch...""" |
| 137 | |
| 138 | if args.hook == "change-merged": |
| 139 | if args.branch == 'master': |
| 140 | if is_direct_release(args.project): |
| 141 | set_fix_released(bugtask) |
| 142 | else: |
Thierry Carrez | 8ea78ac | 2012-11-14 15:47:57 +0100 | [diff] [blame] | 143 | if bugtask.status != u'Fix Released': |
| 144 | set_fix_committed(bugtask) |
Monty Taylor | 6c9634c | 2012-07-28 11:27:47 -0500 | [diff] [blame] | 145 | elif args.branch == 'milestone-proposed': |
| 146 | release_fixcommitted(bugtask) |
| 147 | elif args.branch.startswith('stable/'): |
| 148 | series = args.branch[7:] |
| 149 | # Look for a related task matching the series |
| 150 | for reltask in bugtask.related_tasks: |
Thierry Carrez | 8ea78ac | 2012-11-14 15:47:57 +0100 | [diff] [blame] | 151 | if (reltask.bug_target_name.endswith("/" + series) and |
Monty Taylor | da3bada | 2012-11-22 09:38:22 -0800 | [diff] [blame^] | 152 | reltask.status != u'Fix Released'): |
Monty Taylor | 6c9634c | 2012-07-28 11:27:47 -0500 | [diff] [blame] | 153 | # Use fixcommitted if there is any |
| 154 | set_fix_committed(reltask) |
| 155 | break |
| 156 | else: |
| 157 | # Use tagging if there isn't any |
| 158 | tag_in_branchname(bugtask, args.branch) |
| 159 | |
| 160 | add_change_merged_message(bugtask, args.change_url, args.project, |
| 161 | args.commit, args.submitter, args.branch, |
| 162 | git_log) |
| 163 | |
| 164 | if args.hook == "patchset-created": |
| 165 | if args.branch == 'master': |
Thierry Carrez | 8ea78ac | 2012-11-14 15:47:57 +0100 | [diff] [blame] | 166 | if bugtask.status not in [u'Fix Committed', u'Fix Released']: |
| 167 | set_in_progress(bugtask, launchpad, args.uploader, |
| 168 | args.change_url) |
Monty Taylor | 6c9634c | 2012-07-28 11:27:47 -0500 | [diff] [blame] | 169 | elif args.branch.startswith('stable/'): |
| 170 | series = args.branch[7:] |
| 171 | for reltask in bugtask.related_tasks: |
Thierry Carrez | 8ea78ac | 2012-11-14 15:47:57 +0100 | [diff] [blame] | 172 | if (reltask.bug_target_name.endswith("/" + series) and |
Monty Taylor | da3bada | 2012-11-22 09:38:22 -0800 | [diff] [blame^] | 173 | reltask.status not in [u'Fix Committed', |
| 174 | u'Fix Released']): |
Monty Taylor | 6c9634c | 2012-07-28 11:27:47 -0500 | [diff] [blame] | 175 | set_in_progress(reltask, launchpad, |
| 176 | args.uploader, args.change_url) |
| 177 | break |
| 178 | |
| 179 | if args.patchset == '1': |
| 180 | add_change_proposed_message(bugtask, args.change_url, |
| 181 | args.project, args.branch) |
| 182 | |
| 183 | |
| 184 | def find_bugs(launchpad, git_log, args): |
| 185 | """Find bugs referenced in the git log and return related bugtasks""" |
| 186 | |
| 187 | bug_regexp = r'([Bb]ug|[Ll][Pp])[\s#:]*(\d+)' |
| 188 | tokens = re.split(bug_regexp, git_log) |
| 189 | |
| 190 | # Extract unique bug tasks |
| 191 | bugtasks = {} |
| 192 | for token in tokens: |
| 193 | if re.match('^\d+$', token) and (token not in bugtasks): |
| 194 | try: |
| 195 | lp_bug = launchpad.bugs[token] |
| 196 | for lp_task in lp_bug.bug_tasks: |
| 197 | if lp_task.bug_target_name == git2lp(args.project): |
| 198 | bugtasks[token] = lp_task |
| 199 | break |
| 200 | except KeyError: |
| 201 | # Unknown bug |
| 202 | pass |
| 203 | |
| 204 | return bugtasks.values() |
| 205 | |
| 206 | |
| 207 | def extract_git_log(args): |
| 208 | """Extract git log of all merged commits""" |
| 209 | cmd = ['git', |
| 210 | '--git-dir=' + BASE_DIR + '/git/' + args.project + '.git', |
| 211 | 'log', '--no-merges', args.commit + '^1..' + args.commit] |
| 212 | return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] |
| 213 | |
| 214 | |
| 215 | def main(): |
| 216 | parser = argparse.ArgumentParser() |
| 217 | parser.add_argument('hook') |
| 218 | #common |
| 219 | parser.add_argument('--change', default=None) |
| 220 | parser.add_argument('--change-url', default=None) |
| 221 | parser.add_argument('--project', default=None) |
| 222 | parser.add_argument('--branch', default=None) |
| 223 | parser.add_argument('--commit', default=None) |
| 224 | #change-merged |
| 225 | parser.add_argument('--submitter', default=None) |
| 226 | #patchset-created |
| 227 | parser.add_argument('--uploader', default=None) |
| 228 | parser.add_argument('--patchset', default=None) |
| 229 | |
| 230 | args = parser.parse_args() |
| 231 | |
| 232 | # Connect to Launchpad |
| 233 | launchpad = Launchpad.login_with('Gerrit User Sync', LPNET_SERVICE_ROOT, |
| 234 | GERRIT_CACHE_DIR, |
| 235 | credentials_file=GERRIT_CREDENTIALS, |
| 236 | version='devel') |
| 237 | |
| 238 | # Get git log |
| 239 | git_log = extract_git_log(args) |
| 240 | |
| 241 | # Process bugtasks found in git log |
| 242 | for bugtask in find_bugs(launchpad, git_log, args): |
| 243 | process_bugtask(launchpad, bugtask, git_log, args) |