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