From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: mx.groups.io; dkim=pass header.i=@linaro.org header.s=google header.b=F4fAMQV3; spf=pass (domain: linaro.org, ip: 209.85.221.67, mailfrom: leif.lindholm@linaro.org) Received: from mail-wr1-f67.google.com (mail-wr1-f67.google.com [209.85.221.67]) by groups.io with SMTP; Fri, 14 Jun 2019 13:21:29 -0700 Received: by mail-wr1-f67.google.com with SMTP id x4so3794343wrt.6 for ; Fri, 14 Jun 2019 13:21:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id; bh=e5dfmlE/UgbEg2EoR52+2X09hUNzL0P6OksYV56uuhg=; b=F4fAMQV3/9HCFOgkW6ucDPMbMz2qC8eQTuL54NcdZvOp6XEGCVd8CtvsxNcdzLg4PL XBEkYCqyxS8skIEamdRn8XvXv1GGP+jMi0/ghm1xSDPrh3s2SlZ0p4dbceKLz/SuQzQZ 8gYAiBTLfOtG6et5rn7SxrrRpBQl/Mje0PnWWDiNB5G8ughclnKka7K9VMVbBkEW552+ K8cI4g2+SzdOlYBrWPwc6+rfHB7I72lnXjLfgSkZdG+SylX3wBvom93AKWFVkbHb71Z0 aYCEdlbEfJVtxOWTQJzeO3j96uw/2UG9vcL/CYh1p8eLXfST1prdtBpEh8Fmklo0O9FZ guoA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=e5dfmlE/UgbEg2EoR52+2X09hUNzL0P6OksYV56uuhg=; b=fqn0+WY7dKLNriR/YQn9kJJ5WNlyuMEv10MnuJrXPZ/ICqcvBpa+3e8gXwilDpSQeS T1uYeCgEyFqIz5tnPDnTepUwa6VvKV5HuPAvcbNS9965fBejC1+RwTysSHypfBUzEyZ8 AohKMwx4E4R6CRVMqUL9vJkttfBA/tcrSm14RSYC2aZlqV8JGDPmQg2zETeMP2K8West Y/m0QufZqht86hFrbNsgD+sxiGMmhe2R6FJv/cDw8dONHBYkl7XrB+S/ALjiQgzAKLMb EmzoOdbmEKfd0+rdnkartT5DawRNEuNTG7A9vBR9rkmUKmCMU4cppI5S/PECivN/3mLr IPIw== X-Gm-Message-State: APjAAAXqpHq4TCEMV//yf5G0l2Mw+dAZmgf7mxk3fc46WBwM/Zw7FKNZ biErLvmV+faW4opwE10ci/cuxTYGig0RdQ== X-Google-Smtp-Source: APXvYqyZDp8FkayLjJHH+9QG3f7cuE9dwAVMEhDdu4qlYRkoulSsM0pc7kA8VD7ur+5018y8V6Ib6Q== X-Received: by 2002:adf:9e89:: with SMTP id a9mr51962815wrf.78.1560543687266; Fri, 14 Jun 2019 13:21:27 -0700 (PDT) Return-Path: Received: from vanye.hemma.eciton.net (cpc92302-cmbg19-2-0-cust304.5-4.cable.virginm.net. [82.1.209.49]) by smtp.gmail.com with ESMTPSA id h1sm2565125wrt.20.2019.06.14.13.21.26 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 14 Jun 2019 13:21:26 -0700 (PDT) From: "Leif Lindholm" To: devel@edk2.groups.io Cc: "Feng, Bob C" , Liming Gao , Andrew Fish , Laszlo Ersek , Michael D Kinney , "Wu, Hao A" Subject: [RFC PATCH 3/3] BaseTools: add GetMaintainer.py script Date: Fri, 14 Jun 2019 21:21:21 +0100 Message-Id: <20190614202121.18952-4-leif.lindholm@linaro.org> X-Mailer: git-send-email 2.11.0 Add a new script GetMaintainer.py that uses the new Maintainer.txt format to determine which addresses to cc on patch submission. Signed-off-by: Leif Lindholm --- BaseTools/Scripts/GetMaintainer.py | 181 +++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 BaseTools/Scripts/GetMaintainer.py diff --git a/BaseTools/Scripts/GetMaintainer.py b/BaseTools/Scripts/GetMaintainer.py new file mode 100644 index 000000000000..616342cdb434 --- /dev/null +++ b/BaseTools/Scripts/GetMaintainer.py @@ -0,0 +1,181 @@ +## @file +# Retrieves the people to request review from on submission of a commit. +# +# Copyright (c) 2019, Linaro Ltd. All rights reserved.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# + +from __future__ import print_function +from collections import defaultdict +from collections import OrderedDict +import argparse +import os +import re +import SetupGit + +EXPRESSIONS = { + 'exclude': re.compile(r'^X:\s*(?P.*?)\r*$'), + 'file': re.compile(r'^F:\s*(?P.*?)\r*$'), + 'list': re.compile(r'^L:\s*(?P.*?)\r*$'), + 'maintainer': re.compile(r'^M:\s*(?P.*<.*?>)\r*$'), + 'reviewer': re.compile(r'^R:\s*(?P.*?)\r*$'), + 'status': re.compile(r'^S:\s*(?P.*?)\r*$'), + 'tree': re.compile(r'^T:\s*(?P.*?)\r*$'), + 'webpage': re.compile(r'^W:\s*(?P.*?)\r*$') +} + +def printsection(section): + """Prints out the dictionary describing a Maintainers.txt section.""" + print('===') + for key in section.keys(): + print("Key: %s" % key) + for item in section[key]: + print(' %s' % item) + +def pattern_to_regex(pattern): + """Takes a string containing regular UNIX path wildcards + and returns a string suitable for matching with regex.""" + + pattern = pattern.replace('.', r'\.') + pattern = pattern.replace('?', r'.') + pattern = pattern.replace('*', r'.*') + + if pattern.endswith('/'): + pattern += r'.*' + elif pattern.endswith('.*'): + pattern = pattern[:-2] + pattern += r'(?!.*?/.*?)' + + return pattern + +def path_in_section(path, section): + """Returns True of False indicating whether the path is covered by + the current section.""" + if not 'file' in section: + return False + + for pattern in section['file']: + regex = pattern_to_regex(pattern) + + match = re.match(regex, path) + if match: + # Check if there is an exclude pattern that applies + for pattern in section['exclude']: + regex = pattern_to_regex(pattern) + + match = re.match(regex, path) + if match: + return False + + return True + + return False + +def get_section_maintainers(path, section): + """Returns a list with email addresses to any M: and R: entries + matching the provided path in the provided section.""" + maintainers = [] + lists = [] + + if path_in_section(path, section): + for address in section['maintainer'], section['reviewer']: + # Convert to list if necessary + if isinstance(address, list): + maintainers += address + else: + lists += [address] + for address in section['list']: + # Convert to list if necessary + if isinstance(address, list): + lists += address + else: + lists += [address] + + return maintainers, lists + +def get_maintainers(path, sections, level=0): + """For 'path', iterates over all sections, returning maintainers + for matching ones.""" + maintainers = [] + lists = [] + for section in sections: + tmp_maint, tmp_lists = get_section_maintainers(path, section) + if tmp_maint: + maintainers += tmp_maint + if tmp_lists: + lists += tmp_lists + + if not maintainers: + print('"%s": no maintainers found, looking for default' % path) + if level == 0: + maintainers = get_maintainers('', sections, level=level + 1) + else: + print("No maintainers set for project.") + if not maintainers: + return None + + return maintainers + lists + +def parse_maintainers_line(line): + """Parse one line of Maintainers.txt, returning any match group and its key.""" + for key, expression in EXPRESSIONS.items(): + match = expression.match(line) + if match: + return key, match.group(key) + return None, None + +def parse_maintainers_file(filename): + """Parse the Maintainers.txt from top-level of repo and + return a list containing dictionaries of all sections.""" + with open(filename, 'r') as text: + line = text.readline() + sectionlist = [] + section = defaultdict(list) + while line: + key, value = parse_maintainers_line(line) + if key and value: + section[key].append(value) + + line = text.readline() + # If end of section (end of file, or non-tag line encountered)... + if not key or not value or not line: + # ...if non-empty, append section to list. + if section: + sectionlist.append(section.copy()) + section.clear() + + return sectionlist + +def get_modified_files(repo, args): + """Returns a list of the files modified by the commit specified in 'args'.""" + commit = repo.commit(args.commit) + return commit.stats.files + +if __name__ == '__main__': + PARSER = argparse.ArgumentParser( + description='Retrieves information on who to cc for review on a given commit') + PARSER.add_argument('commit', + action="store", + help='git revision to examine (default: HEAD)', + nargs='?', + default='HEAD') + ARGS = PARSER.parse_args() + + REPO = SetupGit.locate_repo() + + FILES = get_modified_files(REPO, ARGS) + CONFIG_FILE = os.path.join(REPO.working_dir, 'Maintainers.txt') + + SECTIONS = parse_maintainers_file(CONFIG_FILE) + + ADDRESSES = [] + + for file in FILES: + print(file) + addresslist = get_maintainers(file, SECTIONS) + if addresslist: + ADDRESSES += addresslist + + for address in list(OrderedDict.fromkeys(ADDRESSES)): + print(' %s' % address) -- 2.11.0