| 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 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 | 111 |
| 112 # Mapping of owners to the paths or globs they own. | 112 # Mapping of owners to the paths or globs they own. |
| 113 self._owners_to_paths = {EVERYONE: set()} | 113 self._owners_to_paths = {EVERYONE: set()} |
| 114 | 114 |
| 115 # Mapping of paths to authorized owners. | 115 # Mapping of paths to authorized owners. |
| 116 self._paths_to_owners = {} | 116 self._paths_to_owners = {} |
| 117 | 117 |
| 118 # Mapping reviewers to the preceding comment per file in the OWNERS files. | 118 # Mapping reviewers to the preceding comment per file in the OWNERS files. |
| 119 self.comments = {} | 119 self.comments = {} |
| 120 | 120 |
| 121 # Cache of compiled regexes for _fnmatch() |
| 122 self._fnmatch_cache = {} |
| 123 |
| 121 # Set of paths that stop us from looking above them for owners. | 124 # Set of paths that stop us from looking above them for owners. |
| 122 # (This is implicitly true for the root directory). | 125 # (This is implicitly true for the root directory). |
| 123 self._stop_looking = set(['']) | 126 self._stop_looking = set(['']) |
| 124 | 127 |
| 125 # Set of files which have already been read. | 128 # Set of files which have already been read. |
| 126 self.read_files = set() | 129 self.read_files = set() |
| 127 | 130 |
| 128 def reviewers_for(self, files, author): | 131 def reviewers_for(self, files, author): |
| 129 """Returns a suggested set of reviewers that will cover the files. | 132 """Returns a suggested set of reviewers that will cover the files. |
| 130 | 133 |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 190 def load_data_needed_for(self, files): | 193 def load_data_needed_for(self, files): |
| 191 for f in files: | 194 for f in files: |
| 192 dirpath = self.os_path.dirname(f) | 195 dirpath = self.os_path.dirname(f) |
| 193 while not self._owners_for(dirpath): | 196 while not self._owners_for(dirpath): |
| 194 self._read_owners(self.os_path.join(dirpath, 'OWNERS')) | 197 self._read_owners(self.os_path.join(dirpath, 'OWNERS')) |
| 195 if self._should_stop_looking(dirpath): | 198 if self._should_stop_looking(dirpath): |
| 196 break | 199 break |
| 197 dirpath = self.os_path.dirname(dirpath) | 200 dirpath = self.os_path.dirname(dirpath) |
| 198 | 201 |
| 199 def _should_stop_looking(self, objname): | 202 def _should_stop_looking(self, objname): |
| 200 return any(fnmatch.fnmatch(objname, stop_looking) | 203 return any(self._fnmatch(objname, stop_looking) |
| 201 for stop_looking in self._stop_looking) | 204 for stop_looking in self._stop_looking) |
| 202 | 205 |
| 203 def _owners_for(self, objname): | 206 def _owners_for(self, objname): |
| 204 obj_owners = set() | 207 obj_owners = set() |
| 205 for owned_path, path_owners in self._paths_to_owners.iteritems(): | 208 for owned_path, path_owners in self._paths_to_owners.iteritems(): |
| 206 if fnmatch.fnmatch(objname, owned_path): | 209 if self._fnmatch(objname, owned_path): |
| 207 obj_owners |= path_owners | 210 obj_owners |= path_owners |
| 208 return obj_owners | 211 return obj_owners |
| 209 | 212 |
| 210 def _read_owners(self, path): | 213 def _read_owners(self, path): |
| 211 owners_path = self.os_path.join(self.root, path) | 214 owners_path = self.os_path.join(self.root, path) |
| 212 if not self.os_path.exists(owners_path): | 215 if not self.os_path.exists(owners_path): |
| 213 return | 216 return |
| 214 | 217 |
| 215 if owners_path in self.read_files: | 218 if owners_path in self.read_files: |
| 216 return | 219 return |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 332 # If the same person is in multiple OWNERS files above a given | 335 # If the same person is in multiple OWNERS files above a given |
| 333 # directory, only count the closest one. | 336 # directory, only count the closest one. |
| 334 if not any(current_dir == el[0] for el in all_possible_owners[owner]): | 337 if not any(current_dir == el[0] for el in all_possible_owners[owner]): |
| 335 all_possible_owners[owner].append((current_dir, distance)) | 338 all_possible_owners[owner].append((current_dir, distance)) |
| 336 if self._should_stop_looking(dirname): | 339 if self._should_stop_looking(dirname): |
| 337 break | 340 break |
| 338 dirname = self.os_path.dirname(dirname) | 341 dirname = self.os_path.dirname(dirname) |
| 339 distance += 1 | 342 distance += 1 |
| 340 return all_possible_owners | 343 return all_possible_owners |
| 341 | 344 |
| 345 def _fnmatch(self, filename, pattern): |
| 346 """Same as fnmatch.fnmatch(), but interally caches the compiled regexes.""" |
| 347 matcher = self._fnmatch_cache.get(pattern) |
| 348 if matcher is None: |
| 349 matcher = re.compile(fnmatch.translate(pattern)).match |
| 350 self._fnmatch_cache[pattern] = matcher |
| 351 return matcher(filename) |
| 352 |
| 342 @staticmethod | 353 @staticmethod |
| 343 def total_costs_by_owner(all_possible_owners, dirs): | 354 def total_costs_by_owner(all_possible_owners, dirs): |
| 344 # We want to minimize both the number of reviewers and the distance | 355 # We want to minimize both the number of reviewers and the distance |
| 345 # from the files/dirs needing reviews. The "pow(X, 1.75)" below is | 356 # from the files/dirs needing reviews. The "pow(X, 1.75)" below is |
| 346 # an arbitrarily-selected scaling factor that seems to work well - it | 357 # an arbitrarily-selected scaling factor that seems to work well - it |
| 347 # will select one reviewer in the parent directory over three reviewers | 358 # will select one reviewer in the parent directory over three reviewers |
| 348 # in subdirs, but not one reviewer over just two. | 359 # in subdirs, but not one reviewer over just two. |
| 349 result = {} | 360 result = {} |
| 350 for owner in all_possible_owners: | 361 for owner in all_possible_owners: |
| 351 total_distance = 0 | 362 total_distance = 0 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 362 @staticmethod | 373 @staticmethod |
| 363 def lowest_cost_owner(all_possible_owners, dirs): | 374 def lowest_cost_owner(all_possible_owners, dirs): |
| 364 total_costs_by_owner = Database.total_costs_by_owner(all_possible_owners, | 375 total_costs_by_owner = Database.total_costs_by_owner(all_possible_owners, |
| 365 dirs) | 376 dirs) |
| 366 # Return the lowest cost owner. In the case of a tie, pick one randomly. | 377 # Return the lowest cost owner. In the case of a tie, pick one randomly. |
| 367 lowest_cost = min(total_costs_by_owner.itervalues()) | 378 lowest_cost = min(total_costs_by_owner.itervalues()) |
| 368 lowest_cost_owners = filter( | 379 lowest_cost_owners = filter( |
| 369 lambda owner: total_costs_by_owner[owner] == lowest_cost, | 380 lambda owner: total_costs_by_owner[owner] == lowest_cost, |
| 370 total_costs_by_owner) | 381 total_costs_by_owner) |
| 371 return random.Random().choice(lowest_cost_owners) | 382 return random.Random().choice(lowest_cost_owners) |
| OLD | NEW |