]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Scripts/GetMaintainer.py
UefiCpuPkg: Move AsmRelocateApLoopStart from Mpfuncs.nasm to AmdSev.nasm
[mirror_edk2.git] / BaseTools / Scripts / GetMaintainer.py
1 ## @file
2 # Retrieves the people to request review from on submission of a commit.
3 #
4 # Copyright (c) 2019, Linaro Ltd. All rights reserved.<BR>
5 #
6 # SPDX-License-Identifier: BSD-2-Clause-Patent
7 #
8
9 from __future__ import print_function
10 from collections import defaultdict
11 from collections import OrderedDict
12 import argparse
13 import os
14 import re
15 import SetupGit
16
17 EXPRESSIONS = {
18 'exclude': re.compile(r'^X:\s*(?P<exclude>.*?)\r*$'),
19 'file': re.compile(r'^F:\s*(?P<file>.*?)\r*$'),
20 'list': re.compile(r'^L:\s*(?P<list>.*?)\r*$'),
21 'maintainer': re.compile(r'^M:\s*(?P<maintainer>.*?)\r*$'),
22 'reviewer': re.compile(r'^R:\s*(?P<reviewer>.*?)\r*$'),
23 'status': re.compile(r'^S:\s*(?P<status>.*?)\r*$'),
24 'tree': re.compile(r'^T:\s*(?P<tree>.*?)\r*$'),
25 'webpage': re.compile(r'^W:\s*(?P<webpage>.*?)\r*$')
26 }
27
28 def printsection(section):
29 """Prints out the dictionary describing a Maintainers.txt section."""
30 print('===')
31 for key in section.keys():
32 print("Key: %s" % key)
33 for item in section[key]:
34 print(' %s' % item)
35
36 def pattern_to_regex(pattern):
37 """Takes a string containing regular UNIX path wildcards
38 and returns a string suitable for matching with regex."""
39
40 pattern = pattern.replace('.', r'\.')
41 pattern = pattern.replace('?', r'.')
42 pattern = pattern.replace('*', r'.*')
43
44 if pattern.endswith('/'):
45 pattern += r'.*'
46 elif pattern.endswith('.*'):
47 pattern = pattern[:-2]
48 pattern += r'(?!.*?/.*?)'
49
50 return pattern
51
52 def path_in_section(path, section):
53 """Returns True of False indicating whether the path is covered by
54 the current section."""
55 if not 'file' in section:
56 return False
57
58 for pattern in section['file']:
59 regex = pattern_to_regex(pattern)
60
61 match = re.match(regex, path)
62 if match:
63 # Check if there is an exclude pattern that applies
64 for pattern in section['exclude']:
65 regex = pattern_to_regex(pattern)
66
67 match = re.match(regex, path)
68 if match:
69 return False
70
71 return True
72
73 return False
74
75 def get_section_maintainers(path, section):
76 """Returns a list with email addresses to any M: and R: entries
77 matching the provided path in the provided section."""
78 maintainers = []
79 lists = []
80 nowarn_status = ['Supported', 'Maintained']
81
82 if path_in_section(path, section):
83 for status in section['status']:
84 if status not in nowarn_status:
85 print('WARNING: Maintained status for "%s" is \'%s\'!' % (path, status))
86 for address in section['maintainer'], section['reviewer']:
87 # Convert to list if necessary
88 if isinstance(address, list):
89 maintainers += address
90 else:
91 lists += [address]
92 for address in section['list']:
93 # Convert to list if necessary
94 if isinstance(address, list):
95 lists += address
96 else:
97 lists += [address]
98
99 return maintainers, lists
100
101 def get_maintainers(path, sections, level=0):
102 """For 'path', iterates over all sections, returning maintainers
103 for matching ones."""
104 maintainers = []
105 lists = []
106 for section in sections:
107 tmp_maint, tmp_lists = get_section_maintainers(path, section)
108 if tmp_maint:
109 maintainers += tmp_maint
110 if tmp_lists:
111 lists += tmp_lists
112
113 if not maintainers:
114 # If no match found, look for match for (nonexistent) file
115 # REPO.working_dir/<default>
116 print('"%s": no maintainers found, looking for default' % path)
117 if level == 0:
118 maintainers = get_maintainers('<default>', sections, level=level + 1)
119 else:
120 print("No <default> maintainers set for project.")
121 if not maintainers:
122 return None
123
124 return maintainers + lists
125
126 def parse_maintainers_line(line):
127 """Parse one line of Maintainers.txt, returning any match group and its key."""
128 for key, expression in EXPRESSIONS.items():
129 match = expression.match(line)
130 if match:
131 return key, match.group(key)
132 return None, None
133
134 def parse_maintainers_file(filename):
135 """Parse the Maintainers.txt from top-level of repo and
136 return a list containing dictionaries of all sections."""
137 with open(filename, 'r') as text:
138 line = text.readline()
139 sectionlist = []
140 section = defaultdict(list)
141 while line:
142 key, value = parse_maintainers_line(line)
143 if key and value:
144 section[key].append(value)
145
146 line = text.readline()
147 # If end of section (end of file, or non-tag line encountered)...
148 if not key or not value or not line:
149 # ...if non-empty, append section to list.
150 if section:
151 sectionlist.append(section.copy())
152 section.clear()
153
154 return sectionlist
155
156 def get_modified_files(repo, args):
157 """Returns a list of the files modified by the commit specified in 'args'."""
158 commit = repo.commit(args.commit)
159 return commit.stats.files
160
161 if __name__ == '__main__':
162 PARSER = argparse.ArgumentParser(
163 description='Retrieves information on who to cc for review on a given commit')
164 PARSER.add_argument('commit',
165 action="store",
166 help='git revision to examine (default: HEAD)',
167 nargs='?',
168 default='HEAD')
169 PARSER.add_argument('-l', '--lookup',
170 help='Find section matches for path LOOKUP',
171 required=False)
172 ARGS = PARSER.parse_args()
173
174 REPO = SetupGit.locate_repo()
175
176 CONFIG_FILE = os.path.join(REPO.working_dir, 'Maintainers.txt')
177
178 SECTIONS = parse_maintainers_file(CONFIG_FILE)
179
180 if ARGS.lookup:
181 FILES = [ARGS.lookup.replace('\\','/')]
182 else:
183 FILES = get_modified_files(REPO, ARGS)
184
185 ADDRESSES = []
186
187 for file in FILES:
188 print(file)
189 addresslist = get_maintainers(file, SECTIONS)
190 if addresslist:
191 ADDRESSES += addresslist
192
193 for address in list(OrderedDict.fromkeys(ADDRESSES)):
194 if '<' in address and '>' in address:
195 address = address.split('>', 1)[0] + '>'
196 print(' %s' % address)