* Update GetMaintainer.py to support an optional GitHub ID at the end of maintainer and reviewer lines. * Remove contents after email address from standard output * Fix minor issue in --lookup to convert file path separators from '\' to '/' to be compatible with regular expression file matching. Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Michael D Kinney <michael.d.kinney@intel.com> Reviewed-by: Bob Feng <bob.c.feng@intel.com>
		
			
				
	
	
		
			197 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			6.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 = []
 | |
|     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.replace('\\','/')]
 | |
|     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)):
 | |
|         if '<' in address and '>' in address:
 | |
|             address = address.split('>', 1)[0] + '>'
 | |
|         print('  %s' % address)
 |