mirror of
https://github.com/acidanthera/audk.git
synced 2025-05-03 14:10:14 +02:00
REF: https://bugzilla.tianocore.org/show_bug.cgi?id=4593 Sort the list of output addresses alphabetically so this script produces the same output even if the order of patches in a patch series is modified such that that order of files processed by this script changes. Use set() logic instead of OrderedDict to accumulate the list of unique addresses that are sorted alphabetically. Cc: Rebecca Cran <rebecca@bsdio.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Bob Feng <bob.c.feng@intel.com> Cc: Yuwei Chen <yuwei.chen@intel.com> Cc: Leif Lindholm <quic_llindhol@quicinc.com> Signed-off-by: Michael D Kinney <michael.d.kinney@intel.com> Acked-by: Rebecca Cran <rebecca@bsdio.com> Reviewed-by: Leif Lindholm <quic_llindhol@quicinc.com>
208 lines
7.2 KiB
Python
208 lines
7.2 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 = []
|
|
reviewers = []
|
|
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']:
|
|
# Convert to list if necessary
|
|
if isinstance(address, list):
|
|
maintainers += address
|
|
else:
|
|
maintainers += [address]
|
|
for address in section['reviewer']:
|
|
# Convert to list if necessary
|
|
if isinstance(address, list):
|
|
reviewers += address
|
|
else:
|
|
reviewers += [address]
|
|
for address in section['list']:
|
|
# Convert to list if necessary
|
|
if isinstance(address, list):
|
|
lists += address
|
|
else:
|
|
lists += [address]
|
|
|
|
return {'maintainers': maintainers, 'reviewers': reviewers, 'lists': lists}
|
|
|
|
def get_maintainers(path, sections, level=0):
|
|
"""For 'path', iterates over all sections, returning maintainers
|
|
for matching ones."""
|
|
maintainers = []
|
|
reviewers = []
|
|
lists = []
|
|
for section in sections:
|
|
recipients = get_section_maintainers(path, section)
|
|
maintainers += recipients['maintainers']
|
|
reviewers += recipients['reviewers']
|
|
lists += recipients['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:
|
|
recipients = get_maintainers('<default>', sections, level=level + 1)
|
|
maintainers += recipients['maintainers']
|
|
reviewers += recipients['reviewers']
|
|
lists += recipients['lists']
|
|
else:
|
|
print("No <default> maintainers set for project.")
|
|
if not maintainers:
|
|
return None
|
|
|
|
return {'maintainers': maintainers, 'reviewers': reviewers, 'lists': 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.replace('\\','/')]
|
|
else:
|
|
FILES = get_modified_files(REPO, ARGS)
|
|
|
|
# Accumulate a sorted list of addresses
|
|
ADDRESSES = set([])
|
|
for file in FILES:
|
|
print(file)
|
|
recipients = get_maintainers(file, SECTIONS)
|
|
ADDRESSES |= set(recipients['maintainers'] + recipients['reviewers'] + recipients['lists'])
|
|
ADDRESSES = list(ADDRESSES)
|
|
ADDRESSES.sort()
|
|
|
|
for address in ADDRESSES:
|
|
if '<' in address and '>' in address:
|
|
address = address.split('>', 1)[0] + '>'
|
|
print(' %s' % address)
|