mirror of
https://github.com/acidanthera/audk.git
synced 2025-04-08 17:05:09 +02:00
Adds a new `-g` parameter so that output will also include the GitHub username. This change uses a simple regular expression as opposed to directly returning the original line from the file to make the extraction of GitHub usernames more robust to other changes on the line in the maintainers text file. Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
215 lines
7.5 KiB
Python
215 lines
7.5 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)
|
|
PARSER.add_argument('-g', '--github',
|
|
action='store_true',
|
|
help='Include GitHub usernames in output',
|
|
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, github_id = address.split('>', 1)
|
|
address = address + '>'
|
|
github_id = github_id.strip() if ARGS.github else ''
|
|
|
|
print(' %s %s' % (address, github_id))
|