OLD | NEW |
1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """A database of OWNERS files.""" | 5 """A database of OWNERS files.""" |
6 | 6 |
7 import re | 7 import re |
8 | 8 |
9 | 9 |
10 # If this is present by itself on a line, this means that everyone can review. | 10 # If this is present by itself on a line, this means that everyone can review. |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
54 # Mapping of owners to the paths they own. | 54 # Mapping of owners to the paths they own. |
55 self.owned_by = {EVERYONE: set()} | 55 self.owned_by = {EVERYONE: set()} |
56 | 56 |
57 # Mapping of paths to authorized owners. | 57 # Mapping of paths to authorized owners. |
58 self.owners_for = {} | 58 self.owners_for = {} |
59 | 59 |
60 # Set of paths that stop us from looking above them for owners. | 60 # Set of paths that stop us from looking above them for owners. |
61 # (This is implicitly true for the root directory). | 61 # (This is implicitly true for the root directory). |
62 self.stop_looking = set(['']) | 62 self.stop_looking = set(['']) |
63 | 63 |
64 def ReviewersFor(self, files): | 64 def reviewers_for(self, files): |
65 """Returns a suggested set of reviewers that will cover the set of files. | 65 """Returns a suggested set of reviewers that will cover the set of files. |
66 | 66 |
67 files is a set of paths relative to (and under) self.root.""" | 67 files is a set of paths relative to (and under) self.root.""" |
68 self._CheckPaths(files) | 68 self._check_paths(files) |
69 self._LoadDataNeededFor(files) | 69 self._load_data_needed_for(files) |
70 return self._CoveringSetOfOwnersFor(files) | 70 return self._covering_set_of_owners_for(files) |
71 | 71 |
72 def FilesAreCoveredBy(self, files, reviewers): | 72 def files_are_covered_by(self, files, reviewers): |
73 """Returns whether every file is owned by at least one reviewer.""" | 73 """Returns whether every file is owned by at least one reviewer.""" |
74 return not self.FilesNotCoveredBy(files, reviewers) | 74 return not self.files_not_covered_by(files, reviewers) |
75 | 75 |
76 def FilesNotCoveredBy(self, files, reviewers): | 76 def files_not_covered_by(self, files, reviewers): |
77 """Returns the set of files that are not owned by at least one reviewer.""" | 77 """Returns the set of files that are not owned by at least one reviewer.""" |
78 self._CheckPaths(files) | 78 self._check_paths(files) |
79 self._CheckReviewers(reviewers) | 79 self._check_reviewers(reviewers) |
80 if not reviewers: | 80 if not reviewers: |
81 return files | 81 return files |
82 | 82 |
83 self._LoadDataNeededFor(files) | 83 self._load_data_needed_for(files) |
84 files_by_dir = self._FilesByDir(files) | 84 files_by_dir = self._files_by_dir(files) |
85 covered_dirs = self._DirsCoveredBy(reviewers) | 85 covered_dirs = self._dirs_covered_by(reviewers) |
86 uncovered_files = [] | 86 uncovered_files = [] |
87 for d, files_in_d in files_by_dir.iteritems(): | 87 for d, files_in_d in files_by_dir.iteritems(): |
88 if not self._IsDirCoveredBy(d, covered_dirs): | 88 if not self._is_dir_covered_by(d, covered_dirs): |
89 uncovered_files.extend(files_in_d) | 89 uncovered_files.extend(files_in_d) |
90 return set(uncovered_files) | 90 return set(uncovered_files) |
91 | 91 |
92 def _CheckPaths(self, files): | 92 def _check_paths(self, files): |
93 def _isunder(f, pfx): | 93 def _is_under(f, pfx): |
94 return self.os_path.abspath(self.os_path.join(pfx, f)).startswith(pfx) | 94 return self.os_path.abspath(self.os_path.join(pfx, f)).startswith(pfx) |
95 assert all(_isunder(f, self.os_path.abspath(self.root)) for f in files) | 95 assert all(_is_under(f, self.os_path.abspath(self.root)) for f in files) |
96 | 96 |
97 def _CheckReviewers(self, reviewers): | 97 def _check_reviewers(self, reviewers): |
98 """Verifies each reviewer is a valid email address.""" | 98 """Verifies each reviewer is a valid email address.""" |
99 assert all(self.email_regexp.match(r) for r in reviewers) | 99 assert all(self.email_regexp.match(r) for r in reviewers) |
100 | 100 |
101 def _FilesByDir(self, files): | 101 def _files_by_dir(self, files): |
102 dirs = {} | 102 dirs = {} |
103 for f in files: | 103 for f in files: |
104 dirs.setdefault(self.os_path.dirname(f), []).append(f) | 104 dirs.setdefault(self.os_path.dirname(f), []).append(f) |
105 return dirs | 105 return dirs |
106 | 106 |
107 def _DirsCoveredBy(self, reviewers): | 107 def _dirs_covered_by(self, reviewers): |
108 dirs = self.owned_by[EVERYONE] | 108 dirs = self.owned_by[EVERYONE] |
109 for r in reviewers: | 109 for r in reviewers: |
110 dirs = dirs | self.owned_by.get(r, set()) | 110 dirs = dirs | self.owned_by.get(r, set()) |
111 return dirs | 111 return dirs |
112 | 112 |
113 def _StopLooking(self, dirname): | 113 def _stop_looking(self, dirname): |
114 return dirname in self.stop_looking | 114 return dirname in self.stop_looking |
115 | 115 |
116 def _IsDirCoveredBy(self, dirname, covered_dirs): | 116 def _is_dir_covered_by(self, dirname, covered_dirs): |
117 while not dirname in covered_dirs and not self._StopLooking(dirname): | 117 while not dirname in covered_dirs and not self._stop_looking(dirname): |
118 dirname = self.os_path.dirname(dirname) | 118 dirname = self.os_path.dirname(dirname) |
119 return dirname in covered_dirs | 119 return dirname in covered_dirs |
120 | 120 |
121 def _LoadDataNeededFor(self, files): | 121 def _load_data_needed_for(self, files): |
122 for f in files: | 122 for f in files: |
123 dirpath = self.os_path.dirname(f) | 123 dirpath = self.os_path.dirname(f) |
124 while not dirpath in self.owners_for: | 124 while not dirpath in self.owners_for: |
125 self._ReadOwnersInDir(dirpath) | 125 self._read_owners_in_dir(dirpath) |
126 if self._StopLooking(dirpath): | 126 if self._stop_looking(dirpath): |
127 break | 127 break |
128 dirpath = self.os_path.dirname(dirpath) | 128 dirpath = self.os_path.dirname(dirpath) |
129 | 129 |
130 def _ReadOwnersInDir(self, dirpath): | 130 def _read_owners_in_dir(self, dirpath): |
131 owners_path = self.os_path.join(self.root, dirpath, 'OWNERS') | 131 owners_path = self.os_path.join(self.root, dirpath, 'OWNERS') |
132 if not self.os_path.exists(owners_path): | 132 if not self.os_path.exists(owners_path): |
133 return | 133 return |
134 | 134 |
135 lineno = 0 | 135 lineno = 0 |
136 for line in self.fopen(owners_path): | 136 for line in self.fopen(owners_path): |
137 lineno += 1 | 137 lineno += 1 |
138 line = line.strip() | 138 line = line.strip() |
139 if line.startswith('#'): | 139 if line.startswith('#'): |
140 continue | 140 continue |
141 if line == 'set noparent': | 141 if line == 'set noparent': |
142 self.stop_looking.add(dirpath) | 142 self.stop_looking.add(dirpath) |
143 continue | 143 continue |
144 if self.email_regexp.match(line) or line == EVERYONE: | 144 if self.email_regexp.match(line) or line == EVERYONE: |
145 self.owned_by.setdefault(line, set()).add(dirpath) | 145 self.owned_by.setdefault(line, set()).add(dirpath) |
146 self.owners_for.setdefault(dirpath, set()).add(line) | 146 self.owners_for.setdefault(dirpath, set()).add(line) |
147 continue | 147 continue |
148 raise SyntaxErrorInOwnersFile(owners_path, lineno, line) | 148 raise SyntaxErrorInOwnersFile(owners_path, lineno, line) |
149 | 149 |
150 def _CoveringSetOfOwnersFor(self, files): | 150 def _covering_set_of_owners_for(self, files): |
151 # TODO(dpranke): implement the greedy algorithm for covering sets, and | 151 # TODO(dpranke): implement the greedy algorithm for covering sets, and |
152 # consider returning multiple options in case there are several equally | 152 # consider returning multiple options in case there are several equally |
153 # short combinations of owners. | 153 # short combinations of owners. |
154 every_owner = set() | 154 every_owner = set() |
155 for f in files: | 155 for f in files: |
156 dirname = self.os_path.dirname(f) | 156 dirname = self.os_path.dirname(f) |
157 while dirname in self.owners_for: | 157 while dirname in self.owners_for: |
158 every_owner |= self.owners_for[dirname] | 158 every_owner |= self.owners_for[dirname] |
159 if self._StopLooking(dirname): | 159 if self._stop_looking(dirname): |
160 break | 160 break |
161 dirname = self.os_path.dirname(dirname) | 161 dirname = self.os_path.dirname(dirname) |
162 return every_owner | 162 return every_owner |
OLD | NEW |