| OLD | NEW |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 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 OWNERS files indicate who is allowed to approve changes in a specific directory | 7 OWNERS files indicate who is allowed to approve changes in a specific directory |
| 8 (or who is allowed to make changes without needing approval of another OWNER). | 8 (or who is allowed to make changes without needing approval of another OWNER). |
| 9 Note that all changes must still be reviewed by someone familiar with the code, | 9 Note that all changes must still be reviewed by someone familiar with the code, |
| 10 so you may need approval from both an OWNER and a reviewer in many cases. | 10 so you may need approval from both an OWNER and a reviewer in many cases. |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 122 self._check_paths(files) | 122 self._check_paths(files) |
| 123 self._load_data_needed_for(files) | 123 self._load_data_needed_for(files) |
| 124 suggested_owners = self._covering_set_of_owners_for(files) | 124 suggested_owners = self._covering_set_of_owners_for(files) |
| 125 if EVERYONE in suggested_owners: | 125 if EVERYONE in suggested_owners: |
| 126 if len(suggested_owners) > 1: | 126 if len(suggested_owners) > 1: |
| 127 suggested_owners.remove(EVERYONE) | 127 suggested_owners.remove(EVERYONE) |
| 128 else: | 128 else: |
| 129 suggested_owners = set(['<anyone>']) | 129 suggested_owners = set(['<anyone>']) |
| 130 return suggested_owners | 130 return suggested_owners |
| 131 | 131 |
| 132 # TODO(dpranke): rename to objects_not_covered_by | 132 def files_not_covered_by(self, files, reviewers): |
| 133 def directories_not_covered_by(self, files, reviewers): | 133 """Returns the files not owned by one of the reviewers. |
| 134 """Returns the set of directories that are not owned by a reviewer. | |
| 135 | |
| 136 Determines which of the given files are not owned by at least one of the | |
| 137 reviewers, then returns a set containing the applicable enclosing | |
| 138 directories, i.e. the ones upward from the files that have OWNERS files. | |
| 139 | 134 |
| 140 Args: | 135 Args: |
| 141 files is a sequence of paths relative to (and under) self.root. | 136 files is a sequence of paths relative to (and under) self.root. |
| 142 reviewers is a sequence of strings matching self.email_regexp. | 137 reviewers is a sequence of strings matching self.email_regexp. |
| 143 """ | 138 """ |
| 144 self._check_paths(files) | 139 self._check_paths(files) |
| 145 self._check_reviewers(reviewers) | 140 self._check_reviewers(reviewers) |
| 146 self._load_data_needed_for(files) | 141 self._load_data_needed_for(files) |
| 147 | 142 |
| 148 objs = set() | 143 covered_objs = self._objs_covered_by(reviewers) |
| 149 for f in files: | 144 uncovered_files = [f for f in files |
| 150 if f in self.owners_for: | 145 if not self._is_obj_covered_by(f, covered_objs)] |
| 151 objs.add(f) | |
| 152 else: | |
| 153 objs.add(self.os_path.dirname(f)) | |
| 154 | 146 |
| 155 covered_objs = self._objs_covered_by(reviewers) | 147 return set(uncovered_files) |
| 156 uncovered_objs = [self._enclosing_obj_with_owners(o) for o in objs | |
| 157 if not self._is_obj_covered_by(o, covered_objs)] | |
| 158 | |
| 159 return set(uncovered_objs) | |
| 160 | |
| 161 objects_not_covered_by = directories_not_covered_by | |
| 162 | 148 |
| 163 def _check_paths(self, files): | 149 def _check_paths(self, files): |
| 164 def _is_under(f, pfx): | 150 def _is_under(f, pfx): |
| 165 return self.os_path.abspath(self.os_path.join(pfx, f)).startswith(pfx) | 151 return self.os_path.abspath(self.os_path.join(pfx, f)).startswith(pfx) |
| 166 _assert_is_collection(files) | 152 _assert_is_collection(files) |
| 167 assert all(not self.os_path.isabs(f) and | 153 assert all(not self.os_path.isabs(f) and |
| 168 _is_under(f, self.os_path.abspath(self.root)) for f in files) | 154 _is_under(f, self.os_path.abspath(self.root)) for f in files) |
| 169 | 155 |
| 170 def _check_reviewers(self, reviewers): | 156 def _check_reviewers(self, reviewers): |
| 171 _assert_is_collection(reviewers) | 157 _assert_is_collection(reviewers) |
| 172 assert all(self.email_regexp.match(r) for r in reviewers) | 158 assert all(self.email_regexp.match(r) for r in reviewers) |
| 173 | 159 |
| 174 # TODO(dpranke): Rename to _objs_covered_by and update_callers | 160 def _objs_covered_by(self, reviewers): |
| 175 def _dirs_covered_by(self, reviewers): | 161 objs = self.owned_by[EVERYONE] |
| 176 dirs = self.owned_by[EVERYONE] | |
| 177 for r in reviewers: | 162 for r in reviewers: |
| 178 dirs = dirs | self.owned_by.get(r, set()) | 163 objs = objs | self.owned_by.get(r, set()) |
| 179 return dirs | 164 return objs |
| 180 | 165 |
| 181 _objs_covered_by = _dirs_covered_by | 166 def _stop_looking(self, objname): |
| 167 return objname in self.stop_looking |
| 182 | 168 |
| 183 def _stop_looking(self, dirname): | 169 def _is_obj_covered_by(self, objname, covered_objs): |
| 184 return dirname in self.stop_looking | 170 while not objname in covered_objs and not self._stop_looking(objname): |
| 171 objname = self.os_path.dirname(objname) |
| 172 return objname in covered_objs |
| 185 | 173 |
| 186 # TODO(dpranke): Rename to _is_dir_covered_by and update callers. | 174 def _enclosing_dir_with_owners(self, objname): |
| 187 def _is_dir_covered_by(self, dirname, covered_dirs): | |
| 188 while not dirname in covered_dirs and not self._stop_looking(dirname): | |
| 189 dirname = self.os_path.dirname(dirname) | |
| 190 return dirname in covered_dirs | |
| 191 | |
| 192 _is_obj_covered_by = _is_dir_covered_by | |
| 193 | |
| 194 # TODO(dpranke): Rename to _enclosing_obj_with_owners and update callers. | |
| 195 def _enclosing_dir_with_owners(self, directory): | |
| 196 """Returns the innermost enclosing directory that has an OWNERS file.""" | 175 """Returns the innermost enclosing directory that has an OWNERS file.""" |
| 197 dirpath = directory | 176 dirpath = objname |
| 198 while not dirpath in self.owners_for: | 177 while not dirpath in self.owners_for: |
| 199 if self._stop_looking(dirpath): | 178 if self._stop_looking(dirpath): |
| 200 break | 179 break |
| 201 dirpath = self.os_path.dirname(dirpath) | 180 dirpath = self.os_path.dirname(dirpath) |
| 202 return dirpath | 181 return dirpath |
| 203 | 182 |
| 204 _enclosing_obj_with_owners = _enclosing_dir_with_owners | |
| 205 | |
| 206 def _load_data_needed_for(self, files): | 183 def _load_data_needed_for(self, files): |
| 207 for f in files: | 184 for f in files: |
| 208 dirpath = self.os_path.dirname(f) | 185 dirpath = self.os_path.dirname(f) |
| 209 while not dirpath in self.owners_for: | 186 while not dirpath in self.owners_for: |
| 210 self._read_owners_in_dir(dirpath) | 187 self._read_owners_in_dir(dirpath) |
| 211 if self._stop_looking(dirpath): | 188 if self._stop_looking(dirpath): |
| 212 break | 189 break |
| 213 dirpath = self.os_path.dirname(dirpath) | 190 dirpath = self.os_path.dirname(dirpath) |
| 214 | 191 |
| 215 def _read_owners_in_dir(self, dirpath): | 192 def _read_owners_in_dir(self, dirpath): |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 309 if num_directories_owned: | 286 if num_directories_owned: |
| 310 total_costs_by_owner[owner] = (total_distance / | 287 total_costs_by_owner[owner] = (total_distance / |
| 311 pow(num_directories_owned, 1.75)) | 288 pow(num_directories_owned, 1.75)) |
| 312 | 289 |
| 313 # Return the lowest cost owner. In the case of a tie, pick one randomly. | 290 # Return the lowest cost owner. In the case of a tie, pick one randomly. |
| 314 lowest_cost = min(total_costs_by_owner.itervalues()) | 291 lowest_cost = min(total_costs_by_owner.itervalues()) |
| 315 lowest_cost_owners = filter( | 292 lowest_cost_owners = filter( |
| 316 lambda owner: total_costs_by_owner[owner] == lowest_cost, | 293 lambda owner: total_costs_by_owner[owner] == lowest_cost, |
| 317 total_costs_by_owner) | 294 total_costs_by_owner) |
| 318 return random.Random().choice(lowest_cost_owners) | 295 return random.Random().choice(lowest_cost_owners) |
| OLD | NEW |