Alpha version of add_reviewers plugin
authorAlexander Kanevskiy <alexander.kanevskiy@intel.com>
Tue, 13 Aug 2013 18:44:43 +0000 (21:44 +0300)
committerAlexander Kanevskiy <alexander.kanevskiy@intel.com>
Tue, 13 Aug 2013 18:44:43 +0000 (21:44 +0300)
plugins/__init__.py [new file with mode: 0644]
plugins/add_reviewers.py [new file with mode: 0644]

diff --git a/plugins/__init__.py b/plugins/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/plugins/add_reviewers.py b/plugins/add_reviewers.py
new file mode 100644 (file)
index 0000000..0f9a0a6
--- /dev/null
@@ -0,0 +1,194 @@
+#!/usr/bin/env
+# -*- coding: UTF-8 -*-
+# vim: sw=4 ts=4 expandtab ai
+#
+# Copyright (c) 2013 Intel, Inc.
+# License: GPLv2
+# Author: Alexander Kanevskiy <alexander.kanevskiy@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+
+"""
+Add reviewers
+"""
+
+import netrc
+import urlparse
+import types
+import re
+from gerritrest import GerritREST
+
+from pprint import pformat
+
+def to_bool(arg):
+    """Convert string to bool"""
+    if isinstance(arg, types.StringTypes):
+        return bool(arg.lower() in ("true", "yes", "1"))
+    else:
+        return bool(arg)
+
+
+def patchset_created(hook = None, config = None, logger = None, params = None,
+                        extra_params = None):
+
+    if not config.has_option("gerrit", "url"):
+        logger.error("Gerrit URL is not defined in config!")
+        return 1
+
+    gerrit_url = config.get("gerrit", "url")
+
+    if not params.change:
+        logger.warning("Change not defined")
+        return 1
+    if not params.project:
+        logger.warning("Project not defined")
+        return 1
+    if not params.branch:
+        logger.warning("Branch not defined")
+        return 1
+
+    if config.has_option("gerrit", "username") \
+                and config.has_option("gerrit", "password"):
+        user = config.get("gerrit", "username")
+        password = config.get("gerrit", "password")
+    else:
+        # Fallback to ~/.netrc
+        try:
+            user, _, password = \
+                    netrc.netrc().hosts[urlparse.urlparse(gerrit_url).netloc]
+        except KeyError, exc:
+            logger.error("Unable to find credentials in ~/.netrc")
+            return -1
+
+    rules = {}
+    need_project_groups = False
+    need_files = False
+    need_owners = False
+    rules_enabled = False
+
+    for section in config.sections():
+        if len(section) < 17 or section[:16] not in \
+                ("patchset_created", "patchset-created"):
+            continue
+        rule = section[17:]
+        rules[rule] = dict(config.items(section))
+        rules[rule]['enabled'] = to_bool(rules[rule].get('enabled', None))
+        if rules[rule]['enabled']:
+            rules_enabled = True
+            rule_type = rules[rule].get('type', None)
+            if rule_type == 'project_group':
+                need_project_groups = True
+            elif rule_type == 'change_file':
+                need_files = True
+            elif rule_type == 'author':
+                need_owners = True
+            else:
+                logger.warning("Unknown type(%s) in %s", rule_type, section)
+
+    if not rules_enabled:
+        logger.warning("No rules enabled")
+        return 0
+
+    gerrit = GerritREST(gerrit_url, user, password)
+
+    # get change information from gerrit
+    change_query = "change:%s+project:%s+branch:%s" % \
+                (params.change, params.project, params.branch)
+
+    logger.debug("Getting information about change: %s", change_query)
+    change_info = gerrit.get_changes(query=change_query, all_files=need_files,
+        all_revisions=True, detailed_accounts=need_owners, detailed_labels=True)
+    if not change_info:
+        logger.error("Change not found!")
+        return 2
+    elif len(change_info) > 1:
+        logger.warning("More than one change like that found! Using first one.")
+    change_info = change_info[0]
+
+    if need_project_groups:
+        logger.debug("Getting groups info for project %s", params.project)
+        project_groups = gerrit.get_groups(project=params.project)
+
+    reviewers = []
+    reviewer_groups = []
+    for rule in rules:
+        if not rules[rule]['enabled']:
+            continue
+        rule_type = rules[rule].get('type', None)
+        if rule_type == 'project_group':
+            if not rules[rule].get('match', None):
+                logger.warning("No match defined for %s", rule)
+                continue
+            group_match = re.compile(rules[rule]['match'])
+            for group in project_groups:
+                if group_match.search(group):
+                    logger.debug("[%s] Matched group %s", rule, group)
+                    reviewer_groups.append(group)
+        elif rule_type == 'change_file':
+            if not rules[rule].get('match', None):
+                logger.warning("No match defined for %s", rule)
+                continue
+            file_match = re.compile(rules[rule]['match'])
+            if params.commit and params.commit in change_info['revisions']:
+                files = change_info['revisions'][params.commit].get('files', {})
+            else:
+                files = change_info['revisions']\
+                    [change_info['current_revision']].get('files',{})
+            matched = False
+            for change_file in files:
+                if file_match.search(change_file):
+                    matched = True
+                    logger.debug("[%s] Matched file: %s", rule, change_file)
+                    break
+            if matched:
+                for reviewer in re.split('\s*[,;]\s*',
+                                        rules[rule].get('reviewer',"")):
+                    reviewers.append(reviewer.strip())
+                for group in re.split('\s*[,;]\s*',
+                                        rules[rule].get('reviewer_group',"")):
+                    reviewer_groups.append(group.strip())
+        elif rule_type == 'author':
+            if not rules[rule].get('match', None):
+                logger.warning("No match defined for %s", rule)
+                continue
+            owner_match = re.compile(rules[rule]['match'], re.IGNORECASE)
+            owner_info = []
+            owner_email = change_info['owner'].get('email', None)
+            if owner_email:
+                owner_info.append(owner_email)
+            owner_name = change_info['owner'].get('name', None)
+            if owner_name:
+                owner_info.append(owner_name)
+            logger.debug("match: %s Author: %s", rules[rule]['match'], owner_info)
+            matched = False
+            for info in owner_info:
+                if owner_match.search(info):
+                    matched = True
+                    logger.debug("[%s] Matched owner: %s", rule, info)
+                    break
+            if matched:
+                for reviewer in re.split('\s*[,;]\s*',
+                                        rules[rule].get('reviewer',"")):
+                    reviewers.append(reviewer.strip())
+                for group in re.split('\s*[,;]\s*',
+                                        rules[rule].get('reviewer_group',"")):
+                    reviewer_groups.append(group.strip())
+
+    if reviewers:
+        for reviewer in reviewers:
+            logger.debug("Adding reviewer %s", reviewer)
+    if reviewer_groups:
+        for reviewer_group in reviewer_groups:
+            logger.debug("Adding reviewer group %s", reviewer_group)
+
+
+
+