blob: ab0ee2ca1547674de4e30714cc083a38d89d58af [file] [log] [blame]
Monty Taylorf45f6ca2012-05-01 17:11:48 -04001#!/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
20from launchpadlib.launchpad import Launchpad
21from launchpadlib.uris import LPNET_SERVICE_ROOT
22import os
23import argparse
24import re
25import subprocess
26
27
28BASE_DIR = '/home/gerrit2/review_site'
29GERRIT_CACHE_DIR = os.path.expanduser(os.environ.get('GERRIT_CACHE_DIR',
30 '~/.launchpadlib/cache'))
31GERRIT_CREDENTIALS = os.path.expanduser(os.environ.get('GERRIT_CREDENTIALS',
32 '~/.launchpadlib/creds'))
33
34
35def add_change_proposed_message(bugtask, change_url, project, branch):
36 subject = 'Fix proposed to %s (%s)' % (short_project(project), branch)
37 body = 'Fix proposed to branch: %s\nReview: %s' % (branch, change_url)
38 bugtask.bug.newMessage(subject=subject, content=body)
39
40
41def add_change_merged_message(bugtask, change_url, project, commit,
42 submitter, branch, git_log):
43 subject = 'Fix merged to %s (%s)' % (short_project(project), branch)
44 git_url = 'http://github.com/%s/commit/%s' % (project, commit)
45 body = '''Reviewed: %s
46Committed: %s
47Submitter: %s
48Branch: %s\n''' % (change_url, git_url, submitter, branch)
49 body = body + '\n' + git_log
50 bugtask.bug.newMessage(subject=subject, content=body)
51
52
53def set_in_progress(bugtask, launchpad, uploader, change_url):
54 """Set bug In progress with assignee being the uploader"""
55
56 # Retrieve uploader from Launchpad. Use email as search key if
57 # provided, and only set if there is a clear match.
58 try:
59 searchkey = uploader[uploader.rindex("(") + 1:-1]
60 except ValueError:
61 searchkey = uploader
62 persons = launchpad.people.findPerson(text=searchkey)
63 if len(persons) == 1:
64 bugtask.assignee = persons[0]
65
66 bugtask.status = "In Progress"
67 bugtask.lp_save()
68
69
70def set_fix_committed(bugtask):
71 """Set bug fix committed"""
72
73 bugtask.status = "Fix Committed"
74 bugtask.lp_save()
75
76
Monty Taylor962d8ea2012-05-19 10:04:01 -040077def set_fix_released(bugtask):
78 """Set bug fix released"""
79
80 bugtask.status = "Fix Released"
81 bugtask.lp_save()
82
83
Monty Taylorf45f6ca2012-05-01 17:11:48 -040084def release_fixcommitted(bugtask):
85 """Set bug FixReleased if it was FixCommitted"""
86
87 if bugtask.status == u'Fix Committed':
Monty Taylor962d8ea2012-05-19 10:04:01 -040088 set_fix_released(bugtask)
Monty Taylorf45f6ca2012-05-01 17:11:48 -040089
90
91def tag_in_branchname(bugtask, branch):
92 """Tag bug with in-branch-name tag (if name is appropriate)"""
93
94 lp_bug = bugtask.bug
95 branch_name = branch.replace('/', '-')
96 if branch_name.replace('-', '').isalnum():
97 lp_bug.tags = lp_bug.tags + ["in-%s" % branch_name]
98 lp_bug.tags.append("in-%s" % branch_name)
99 lp_bug.lp_save()
100
101
102def short_project(full_project_name):
103 """Return the project part of the git repository name"""
104 return full_project_name.split('/')[-1]
105
106
107def git2lp(full_project_name):
108 """Convert Git repo name to Launchpad project"""
109 project_map = {
Monty Taylorde5f3f82012-05-03 13:11:53 -0700110 'openstack/python-cinderclient': 'cinder',
Monty Taylorf45f6ca2012-05-01 17:11:48 -0400111 'openstack/python-glanceclient': 'glance',
112 'openstack/python-keystoneclient': 'keystone',
113 'openstack/python-melangeclient': 'melange',
114 'openstack/python-novaclient': 'nova',
115 'openstack/python-quantumclient': 'quantum',
116 'openstack/openstack-ci-puppet': 'openstack-ci',
117 'openstack-ci/devstack-gate': 'openstack-ci',
Monty Taylor144a7222012-06-10 15:33:11 -0400118 'openstack-ci/gerrit': 'openstack-ci',
Monty Taylor962d8ea2012-05-19 10:04:01 -0400119 'openstack-ci/lodgeit': 'openstack-ci',
120 'openstack-ci/meetbot': 'openstack-ci',
Monty Taylorf45f6ca2012-05-01 17:11:48 -0400121 }
122 return project_map.get(full_project_name, short_project(full_project_name))
123
124
Monty Taylor962d8ea2012-05-19 10:04:01 -0400125def is_direct_release(full_project_name):
126 """Test against a list of projects who directly release changes."""
Thierry Carrez617b8d72012-05-23 14:18:43 +0200127 return full_project_name in [
Monty Taylor962d8ea2012-05-19 10:04:01 -0400128 'openstack-ci/devstack-gate',
129 'openstack-ci/lodgeit',
130 'openstack-ci/meetbot',
131 'openstack-dev/devstack',
132 'openstack/openstack-ci',
133 'openstack/openstack-ci-puppet',
134 'openstack/openstack-manuals',
135 ]
136
137
Monty Taylorf45f6ca2012-05-01 17:11:48 -0400138def process_bugtask(launchpad, bugtask, git_log, args):
139 """Apply changes to bugtask, based on hook / branch..."""
140
141 if args.hook == "change-merged":
142 if args.branch == 'master':
Monty Taylor962d8ea2012-05-19 10:04:01 -0400143 if is_direct_release(args.project):
144 set_fix_released(bugtask)
145 else:
146 set_fix_committed(bugtask)
Monty Taylorf45f6ca2012-05-01 17:11:48 -0400147 elif args.branch == 'milestone-proposed':
148 release_fixcommitted(bugtask)
Thierry Carrez7b961cb2012-05-23 16:48:26 +0200149 elif args.branch.startswith('stable/'):
150 series = args.branch[7:]
151 # Look for a related task matching the series
152 for reltask in bugtask.related_tasks:
153 if reltask.bug_target_name.endswith("/" + series):
154 # Use fixcommitted if there is any
155 set_fix_committed(reltask)
156 break
157 else:
158 # Use tagging if there isn't any
159 tag_in_branchname(bugtask, args.branch)
160
Monty Taylorf45f6ca2012-05-01 17:11:48 -0400161 add_change_merged_message(bugtask, args.change_url, args.project,
162 args.commit, args.submitter, args.branch,
163 git_log)
164
165 if args.hook == "patchset-created":
166 if args.branch == 'master':
167 set_in_progress(bugtask, launchpad, args.uploader, args.change_url)
Thierry Carrez7b961cb2012-05-23 16:48:26 +0200168 elif args.branch.startswith('stable/'):
169 series = args.branch[7:]
170 for reltask in bugtask.related_tasks:
171 if reltask.bug_target_name.endswith("/" + series):
172 set_in_progress(reltask, launchpad,
173 args.uploader, args.change_url)
174 break
175
Monty Taylorf45f6ca2012-05-01 17:11:48 -0400176 if args.patchset == '1':
177 add_change_proposed_message(bugtask, args.change_url,
178 args.project, args.branch)
179
180
181def find_bugs(launchpad, git_log, args):
182 """Find bugs referenced in the git log and return related bugtasks"""
183
184 bug_regexp = r'([Bb]ug|[Ll][Pp])[\s#:]*(\d+)'
185 tokens = re.split(bug_regexp, git_log)
186
187 # Extract unique bug tasks
188 bugtasks = {}
189 for token in tokens:
190 if re.match('^\d+$', token) and (token not in bugtasks):
191 try:
192 lp_bug = launchpad.bugs[token]
193 for lp_task in lp_bug.bug_tasks:
194 if lp_task.bug_target_name == git2lp(args.project):
195 bugtasks[token] = lp_task
196 break
197 except KeyError:
198 # Unknown bug
199 pass
200
201 return bugtasks.values()
202
203
204def extract_git_log(args):
205 """Extract git log of all merged commits"""
206 cmd = ['git',
207 '--git-dir=' + BASE_DIR + '/git/' + args.project + '.git',
208 'log', '--no-merges', args.commit + '^1..' + args.commit]
209 return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
210
211
212def main():
213 parser = argparse.ArgumentParser()
214 parser.add_argument('hook')
215 #common
216 parser.add_argument('--change', default=None)
217 parser.add_argument('--change-url', default=None)
218 parser.add_argument('--project', default=None)
219 parser.add_argument('--branch', default=None)
220 parser.add_argument('--commit', default=None)
221 #change-merged
222 parser.add_argument('--submitter', default=None)
223 #patchset-created
224 parser.add_argument('--uploader', default=None)
225 parser.add_argument('--patchset', default=None)
226
227 args = parser.parse_args()
228
229 # Connect to Launchpad
230 launchpad = Launchpad.login_with('Gerrit User Sync', LPNET_SERVICE_ROOT,
231 GERRIT_CACHE_DIR,
232 credentials_file=GERRIT_CREDENTIALS,
233 version='devel')
234
235 # Get git log
236 git_log = extract_git_log(args)
237
238 # Process bugtasks found in git log
239 for bugtask in find_bugs(launchpad, git_log, args):
240 process_bugtask(launchpad, bugtask, git_log, args)
241
242
243if __name__ == '__main__':
244 main()