Sync job files with internal CI
Sync job files with platform-ci commit:
539c151d0cd99a5e6ca6c0e6966f6d8579fe864e
Signed-off-by: Zelalem <zelalem.aweke@arm.com>
Change-Id: Ida470e00da76188ce3987d1fa93ec758b5e0f23a
diff --git a/job/tf-gerrit-bot/gerrit_bot.py b/job/tf-gerrit-bot/gerrit_bot.py
new file mode 100644
index 0000000..1bad974
--- /dev/null
+++ b/job/tf-gerrit-bot/gerrit_bot.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2019, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#
+# Assigns reviewers according to maintainers file.
+
+import argparse
+import os
+from pygerrit2 import GerritRestAPI, HTTPBasicAuth
+import re
+
+DEFAULT_GERRIT_URL = 'https://review.trustedfirmware.org'
+DEFAULT_GERRIT_PROJECT_NAME = 'TF-A/trusted-firmware-a'
+DEFAULT_MAINTAINERS_FILE_NAME = 'maintainers.rst'
+
+# Commit message is returned in a file list, ignore it
+COMMIT_MSG_FILE = '/COMMIT_MSG'
+
+def connect_to_gerrit(gerrit_url, gerrit_user, gerrit_password):
+ '''
+ Connect to Gerrit server.
+ The password is not a plaintext password,
+ it can be obtained from Profile/Settings/HTTP Password page.
+ Returns GerritRestAPI class.
+ '''
+
+ auth = HTTPBasicAuth(gerrit_user, gerrit_password)
+ return GerritRestAPI(url=gerrit_url, auth=auth)
+
+
+def get_open_changes(rest_api, project_name):
+ '''
+ Get list of open reviews for the project.
+ '''
+
+ # Pass DETAILED_ACCOUNTS to get owner username
+ return rest_api.get("/changes/?q=status:open%20project:" + project_name + "&o=DETAILED_ACCOUNTS")
+
+
+def get_files(rest_api, change_id):
+ '''
+ Get list of changed files for the review.
+ Commit message is removed from the list.
+ '''
+
+ files_list = rest_api.get("/changes/" + change_id + "/revisions/current/files/")
+ del files_list[COMMIT_MSG_FILE]
+
+ return files_list
+
+
+def add_reviewer(rest_api, change_id, username, dry_run):
+ '''
+ Add reviewer to the review.
+ '''
+
+ endpoint = "/changes/" + change_id + "/reviewers"
+ kwargs = {"data": {"reviewer": username}}
+
+ # Exception is thrown if username is wrong, so just print it
+ try:
+ if not dry_run:
+ rest_api.post(endpoint, **kwargs)
+ except Exception as e:
+ print(" Add reviewer failed, username: " + str(username))
+ print(" " + str(e))
+ else:
+ print(" Reviewer added, username: " + str(username))
+
+
+def parse_maintainers_file(file_path):
+ '''
+ Parse maintainers file.
+ Returns a dictionary {file_path:set{user1, user2, ...}}
+ '''
+
+ f = open(file_path, encoding='utf8')
+ file_text = f.read()
+ f.close()
+
+ FILE_PREFIX = "\n:F: "
+
+ regex = r"^:G: `(?P<user>.*)`_$(?P<paths>(" + FILE_PREFIX + r".*$)+)"
+ matches = re.finditer(regex, file_text, re.MULTILINE)
+
+ # Create a dictionary {file_path:set{user1, user2, ...}} for faster search
+ result_dict = {}
+
+ for match in matches:
+ user_name = match.group("user")
+
+ paths = match.group("paths").split(FILE_PREFIX)
+ paths.remove("")
+
+ # Fill the dictionary
+ for path in paths:
+ if path not in result_dict:
+ result_dict[path] = set()
+
+ result_dict[path].add(user_name)
+
+ return result_dict
+
+
+def get_file_maintainers(file_path, maintainers_dictionary):
+ '''
+ Returns a set of usernames(mainteiners) for the file.
+ '''
+
+ maintainers = set()
+
+ file = file_path
+
+ # Get maintainers of the file
+ maintainers_set = maintainers_dictionary.get(file)
+ if maintainers_set:
+ maintainers.update(maintainers_set)
+
+ # Get maintainers of the directories
+ while (file > "/"):
+ # Get upper directory on each step.
+ file = os.path.dirname(file)
+ path_to_check = file + "/"
+
+ maintainers_set = maintainers_dictionary.get(path_to_check)
+ if maintainers_set:
+ maintainers.update(maintainers_set)
+
+ return maintainers
+
+
+def assign_reviewers(rest_api, maintainers_dictionary, change, dry_run):
+ '''
+ Assign maintainers to the review.
+ '''
+
+ # It looks like some accounts may not have username
+ owner_username = None
+ if ('username' in change['owner']):
+ owner_username = change['owner']['username']
+
+ print("\nChange: " + str(change['id']))
+ print(" Topic: " + str(change.get('topic')))
+ print(" Owner: " + str(owner_username))
+
+ change_maintainers = set()
+
+ # Get list of all files in the change
+ files = get_files(rest_api, change['id'])
+
+ for file in files:
+ # Get all maintainers of the file
+ file_maintainers = get_file_maintainers(file, maintainers_dictionary)
+
+ if (len(file_maintainers) > 0):
+ print(" File: " + file + " maintainers: " + str(file_maintainers))
+
+ change_maintainers.update(file_maintainers)
+
+ # Don't add owner even if he is a maintainer
+ change_maintainers.discard(owner_username)
+
+ for maintainer in change_maintainers:
+ add_reviewer(rest_api, change['id'], maintainer, dry_run)
+
+
+def parse_cmd_line():
+
+ parser = argparse.ArgumentParser(
+ description="Gerrit bot",
+ epilog="""
+ Assigns reviewers according to maintainers file.
+ """
+ )
+
+ required_group = parser.add_argument_group('required arguments')
+
+ parser.add_argument("--url", "-u",
+ help = """
+ Gerrit URL (default: %(default)s)
+ """,
+ default = DEFAULT_GERRIT_URL)
+
+ parser.add_argument("--project", "-p",
+ help = """
+ Project name (default: %(default)s).
+ """,
+ default = DEFAULT_GERRIT_PROJECT_NAME)
+
+ parser.add_argument("--maintainers", "-m",
+ help = """
+ Path to maintainers file (default: %(default)s).
+ """,
+ default = DEFAULT_MAINTAINERS_FILE_NAME)
+
+ parser.add_argument("--dry-run",
+ help = """
+ Check maintainers, but don't add them (default: %(default)s).
+ """,
+ action='store_true',
+ default = False)
+
+ required_group.add_argument("--user",
+ help = """
+ Gerrit user.
+ """,
+ required = True)
+
+ required_group.add_argument("--password",
+ help="""
+ Gerrit HTTP password.
+ This is NOT a plaintext password.
+ But the value from Profile/Settings/HTTP Password
+ """,
+ required = True)
+
+ return parser.parse_args()
+
+
+if __name__ == '__main__':
+
+ args = parse_cmd_line()
+
+ maintainers_dict = parse_maintainers_file(args.maintainers)
+ rest = connect_to_gerrit(args.url, args.user, args.password)
+ changes = get_open_changes(rest, args.project)
+
+ for change in changes:
+ assign_reviewers(rest, maintainers_dict, change, args.dry_run)