| #!/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 "blueprint FOO" or "bp FOO" and updates |
| # corresponding Launchpad blueprints with links back to the change. |
| |
| import argparse |
| import six |
| from six.moves import configparser |
| import os |
| import re |
| import subprocess |
| |
| from launchpadlib import launchpad |
| from launchpadlib import uris |
| import pymysql |
| |
| from jeepyb import projects as p |
| |
| |
| 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')) |
| GERRIT_CONFIG = os.environ.get('GERRIT_CONFIG', |
| '/home/gerrit2/review_site/etc/gerrit.config') |
| GERRIT_SECURE_CONFIG_DEFAULT = '/home/gerrit2/review_site/etc/secure.config' |
| GERRIT_SECURE_CONFIG = os.environ.get('GERRIT_SECURE_CONFIG', |
| GERRIT_SECURE_CONFIG_DEFAULT) |
| SPEC_RE = re.compile(r'\b(blueprint|bp)\b[ \t]*[#:]?[ \t]*(\S+)', re.I) |
| BODY_RE = re.compile(r'^\s+.*$') |
| |
| |
| def get_broken_config(filename): |
| """gerrit config ini files are broken and have leading tabs.""" |
| text = "" |
| with open(filename, "r") as conf: |
| for line in conf.readlines(): |
| text = "%s%s" % (text, line.lstrip()) |
| |
| fp = six.StringIO(text) |
| c = configparser.ConfigParser() |
| c.readfp(fp) |
| return c |
| |
| GERRIT_CONFIG = get_broken_config(GERRIT_CONFIG) |
| SECURE_CONFIG = get_broken_config(GERRIT_SECURE_CONFIG) |
| DB_HOST = GERRIT_CONFIG.get("database", "hostname") |
| DB_USER = GERRIT_CONFIG.get("database", "username") |
| DB_PASS = SECURE_CONFIG.get("database", "password") |
| DB_DB = GERRIT_CONFIG.get("database", "database") |
| |
| |
| def update_spec(launchpad, project, name, subject, link, topic=None): |
| spec = None |
| |
| if p.is_no_launchpad_blueprints(project): |
| return |
| |
| projects = p.project_to_groups(project) |
| |
| for project in projects: |
| spec = launchpad.projects[project].getSpecification(name=name) |
| if spec: |
| break |
| |
| if not spec: |
| return |
| |
| if spec.whiteboard: |
| wb = spec.whiteboard.strip() |
| else: |
| wb = '' |
| changed = False |
| if topic: |
| topiclink = '%s/#/q/topic:%s' % (link[:link.find('/', 8)], |
| topic) |
| if topiclink not in wb: |
| wb += "\n\n\nGerrit topic: %(link)s" % dict(link=topiclink) |
| changed = True |
| |
| if link not in wb: |
| wb += ("\n\n\nAddressed by: {link}\n" |
| " {subject}\n").format(subject=subject, |
| link=link) |
| changed = True |
| |
| if changed: |
| spec.whiteboard = wb |
| spec.lp_save() |
| |
| |
| def find_specs(launchpad, dbconn, args): |
| git_dir_arg = '--git-dir={base_dir}/git/{project}.git'.format( |
| base_dir=BASE_DIR, |
| project=args.project) |
| git_log = subprocess.Popen(['git', git_dir_arg, 'log', '--no-merges', |
| args.commit + '^1..' + args.commit], |
| stdout=subprocess.PIPE).communicate()[0] |
| |
| change = args.change |
| if '~' in change: |
| # Newer gerrit provides the change argument in this format: |
| # gtest-org%2Fgtest~master~I117f34aaa4253e0b82b98de9077f7188d55c3f33 |
| # So we need to split off the changeid if there is other data in there. |
| change = change.rsplit('~', 1)[1] |
| cur = dbconn.cursor() |
| cur.execute("select subject, topic from changes where change_key=%s", |
| change) |
| subject, topic = cur.fetchone() |
| specs = set([m.group(2) for m in SPEC_RE.finditer(git_log)]) |
| |
| if topic: |
| topicspec = topic.split('/')[-1] |
| specs |= set([topicspec]) |
| |
| for spec in specs: |
| update_spec(launchpad, args.project, spec, subject, |
| args.change_url, topic) |
| |
| |
| 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) |
| parser.add_argument('--topic', default=None) |
| parser.add_argument('--change-owner', default=None) |
| # patchset-abandoned |
| parser.add_argument('--abandoner', default=None) |
| parser.add_argument('--reason', default=None) |
| # change-merged |
| parser.add_argument('--submitter', default=None) |
| parser.add_argument('--newrev', default=None) |
| # patchset-created |
| parser.add_argument('--uploader', default=None) |
| parser.add_argument('--patchset', default=None) |
| parser.add_argument('--is-draft', default=None) |
| parser.add_argument('--kind', default=None) |
| |
| args = parser.parse_args() |
| |
| lpconn = launchpad.Launchpad.login_with( |
| 'Gerrit User Sync', uris.LPNET_SERVICE_ROOT, GERRIT_CACHE_DIR, |
| credentials_file=GERRIT_CREDENTIALS, version='devel') |
| |
| conn = pymysql.connect( |
| host=DB_HOST, user=DB_USER, password=DB_PASS, db=DB_DB) |
| |
| find_specs(lpconn, conn, args) |
| |
| if __name__ == "__main__": |
| main() |