mirror of https://github.com/acidanthera/audk.git
195 lines
6.3 KiB
Python
195 lines
6.3 KiB
Python
## @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 = []
|
|
nowarn_status = ['Supported', 'Maintained']
|
|
|
|
if path_in_section(path, section):
|
|
for status in section['status']:
|
|
if status not in nowarn_status:
|
|
print('WARNING: Maintained status for "%s" is \'%s\'!' % (path, status))
|
|
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)
|