| 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 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 108 # Mapping of owners to the paths they own. | 108 # Mapping of owners to the paths they own. |
| 109 self.owned_by = {EVERYONE: set()} | 109 self.owned_by = {EVERYONE: set()} |
| 110 | 110 |
| 111 # Mapping of paths to authorized owners. | 111 # Mapping of paths to authorized owners. |
| 112 self.owners_for = {} | 112 self.owners_for = {} |
| 113 | 113 |
| 114 # Set of paths that stop us from looking above them for owners. | 114 # Set of paths that stop us from looking above them for owners. |
| 115 # (This is implicitly true for the root directory). | 115 # (This is implicitly true for the root directory). |
| 116 self.stop_looking = set(['']) | 116 self.stop_looking = set(['']) |
| 117 | 117 |
| 118 def reviewers_for(self, files): | 118 def reviewers_for(self, files, author): |
| 119 """Returns a suggested set of reviewers that will cover the files. | 119 """Returns a suggested set of reviewers that will cover the files. |
| 120 | 120 |
| 121 files is a sequence of paths relative to (and under) self.root.""" | 121 files is a sequence of paths relative to (and under) self.root. |
| 122 If author is nonempty, we ensure it is not included in the set returned |
| 123 in order avoid suggesting the author as a reviewer for their own changes.""" |
| 122 self._check_paths(files) | 124 self._check_paths(files) |
| 123 self._load_data_needed_for(files) | 125 self._load_data_needed_for(files) |
| 124 suggested_owners = self._covering_set_of_owners_for(files) | 126 suggested_owners = self._covering_set_of_owners_for(files, author) |
| 125 if EVERYONE in suggested_owners: | 127 if EVERYONE in suggested_owners: |
| 126 if len(suggested_owners) > 1: | 128 if len(suggested_owners) > 1: |
| 127 suggested_owners.remove(EVERYONE) | 129 suggested_owners.remove(EVERYONE) |
| 128 else: | 130 else: |
| 129 suggested_owners = set(['<anyone>']) | 131 suggested_owners = set(['<anyone>']) |
| 130 return suggested_owners | 132 return suggested_owners |
| 131 | 133 |
| 132 def files_not_covered_by(self, files, reviewers): | 134 def files_not_covered_by(self, files, reviewers): |
| 133 """Returns the files not owned by one of the reviewers. | 135 """Returns the files not owned by one of the reviewers. |
| 134 | 136 |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 229 if directive == "set noparent": | 231 if directive == "set noparent": |
| 230 self.stop_looking.add(path) | 232 self.stop_looking.add(path) |
| 231 elif self.email_regexp.match(directive) or directive == EVERYONE: | 233 elif self.email_regexp.match(directive) or directive == EVERYONE: |
| 232 self.owned_by.setdefault(directive, set()).add(path) | 234 self.owned_by.setdefault(directive, set()).add(path) |
| 233 self.owners_for.setdefault(path, set()).add(directive) | 235 self.owners_for.setdefault(path, set()).add(directive) |
| 234 else: | 236 else: |
| 235 raise SyntaxErrorInOwnersFile(owners_path, lineno, | 237 raise SyntaxErrorInOwnersFile(owners_path, lineno, |
| 236 ('%s is not a "set" directive, "*", ' | 238 ('%s is not a "set" directive, "*", ' |
| 237 'or an email address: "%s"' % (line_type, directive))) | 239 'or an email address: "%s"' % (line_type, directive))) |
| 238 | 240 |
| 239 def _covering_set_of_owners_for(self, files): | 241 def _covering_set_of_owners_for(self, files, author): |
| 240 dirs_remaining = set(self._enclosing_dir_with_owners(f) for f in files) | 242 dirs_remaining = set(self._enclosing_dir_with_owners(f) for f in files) |
| 241 all_possible_owners = self._all_possible_owners(dirs_remaining) | 243 all_possible_owners = self._all_possible_owners(dirs_remaining, author) |
| 242 suggested_owners = set() | 244 suggested_owners = set() |
| 243 while dirs_remaining: | 245 while dirs_remaining: |
| 244 owner = self.lowest_cost_owner(all_possible_owners, dirs_remaining) | 246 owner = self.lowest_cost_owner(all_possible_owners, dirs_remaining) |
| 245 suggested_owners.add(owner) | 247 suggested_owners.add(owner) |
| 246 dirs_to_remove = set(el[0] for el in all_possible_owners[owner]) | 248 dirs_to_remove = set(el[0] for el in all_possible_owners[owner]) |
| 247 dirs_remaining -= dirs_to_remove | 249 dirs_remaining -= dirs_to_remove |
| 248 return suggested_owners | 250 return suggested_owners |
| 249 | 251 |
| 250 def _all_possible_owners(self, dirs): | 252 def _all_possible_owners(self, dirs, author): |
| 251 """Returns a list of (potential owner, distance-from-dir) tuples; a | 253 """Returns a list of (potential owner, distance-from-dir) tuples; a |
| 252 distance of 1 is the lowest/closest possible distance (which makes the | 254 distance of 1 is the lowest/closest possible distance (which makes the |
| 253 subsequent math easier).""" | 255 subsequent math easier).""" |
| 254 all_possible_owners = {} | 256 all_possible_owners = {} |
| 255 for current_dir in dirs: | 257 for current_dir in dirs: |
| 256 dirname = current_dir | 258 dirname = current_dir |
| 257 distance = 1 | 259 distance = 1 |
| 258 while True: | 260 while True: |
| 259 for owner in self.owners_for.get(dirname, []): | 261 for owner in self.owners_for.get(dirname, []): |
| 262 if author and owner == author: |
| 263 continue |
| 260 all_possible_owners.setdefault(owner, []) | 264 all_possible_owners.setdefault(owner, []) |
| 261 # If the same person is in multiple OWNERS files above a given | 265 # If the same person is in multiple OWNERS files above a given |
| 262 # directory, only count the closest one. | 266 # directory, only count the closest one. |
| 263 if not any(current_dir == el[0] for el in all_possible_owners[owner]): | 267 if not any(current_dir == el[0] for el in all_possible_owners[owner]): |
| 264 all_possible_owners[owner].append((current_dir, distance)) | 268 all_possible_owners[owner].append((current_dir, distance)) |
| 265 if self._stop_looking(dirname): | 269 if self._stop_looking(dirname): |
| 266 break | 270 break |
| 267 dirname = self.os_path.dirname(dirname) | 271 dirname = self.os_path.dirname(dirname) |
| 268 distance += 1 | 272 distance += 1 |
| 269 return all_possible_owners | 273 return all_possible_owners |
| (...skipping 16 matching lines...) Expand all Loading... |
| 286 if num_directories_owned: | 290 if num_directories_owned: |
| 287 total_costs_by_owner[owner] = (total_distance / | 291 total_costs_by_owner[owner] = (total_distance / |
| 288 pow(num_directories_owned, 1.75)) | 292 pow(num_directories_owned, 1.75)) |
| 289 | 293 |
| 290 # Return the lowest cost owner. In the case of a tie, pick one randomly. | 294 # Return the lowest cost owner. In the case of a tie, pick one randomly. |
| 291 lowest_cost = min(total_costs_by_owner.itervalues()) | 295 lowest_cost = min(total_costs_by_owner.itervalues()) |
| 292 lowest_cost_owners = filter( | 296 lowest_cost_owners = filter( |
| 293 lambda owner: total_costs_by_owner[owner] == lowest_cost, | 297 lambda owner: total_costs_by_owner[owner] == lowest_cost, |
| 294 total_costs_by_owner) | 298 total_costs_by_owner) |
| 295 return random.Random().choice(lowest_cost_owners) | 299 return random.Random().choice(lowest_cost_owners) |
| OLD | NEW |