mirror of https://github.com/acidanthera/audk.git
BaseTools: add GetMaintainer.py script
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 <leif.lindholm@linaro.org> Reviewed-by: Philippe Mathieu-Daude <philmd@redhat.com> Tested-by: Philippe Mathieu-Daude <philmd@redhat.com> Acked-by: Laszlo Ersek <lersek@redhat.com> Acked-by: Liming Gao <liming.gao@intel.com> Reviewed-by: Bob Feng <bob.c.feng@intel.com>
This commit is contained in:
parent
83e7d5c75e
commit
7d0a56c4a1
|
@ -0,0 +1,190 @@
|
|||
## @file
|
||||
# Retrieves the people to request review from on submission of a commit.
|
||||
#
|
||||
# Copyright (c) 2019, Linaro Ltd. All rights reserved.<BR>
|
||||
#
|
||||
# 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<exclude>.*?)\r*$'),
|
||||
'file': re.compile(r'^F:\s*(?P<file>.*?)\r*$'),
|
||||
'list': re.compile(r'^L:\s*(?P<list>.*?)\r*$'),
|
||||
'maintainer': re.compile(r'^M:\s*(?P<maintainer>.*<.*?>)\r*$'),
|
||||
'reviewer': re.compile(r'^R:\s*(?P<reviewer>.*?)\r*$'),
|
||||
'status': re.compile(r'^S:\s*(?P<status>.*?)\r*$'),
|
||||
'tree': re.compile(r'^T:\s*(?P<tree>.*?)\r*$'),
|
||||
'webpage': re.compile(r'^W:\s*(?P<webpage>.*?)\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:
|
||||
# If no match found, look for match for (nonexistent) file
|
||||
# REPO.working_dir/<default>
|
||||
print('"%s": no maintainers found, looking for default' % path)
|
||||
if level == 0:
|
||||
maintainers = get_maintainers('<default>', sections, level=level + 1)
|
||||
else:
|
||||
print("No <default> 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')
|
||||
PARSER.add_argument('-l', '--lookup',
|
||||
help='Find section matches for path LOOKUP',
|
||||
required=False)
|
||||
ARGS = PARSER.parse_args()
|
||||
|
||||
REPO = SetupGit.locate_repo()
|
||||
|
||||
CONFIG_FILE = os.path.join(REPO.working_dir, 'Maintainers.txt')
|
||||
|
||||
SECTIONS = parse_maintainers_file(CONFIG_FILE)
|
||||
|
||||
if ARGS.lookup:
|
||||
FILES = [ARGS.lookup]
|
||||
else:
|
||||
FILES = get_modified_files(REPO, ARGS)
|
||||
|
||||
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)
|
Loading…
Reference in New Issue