Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(58)

Side by Side Diff: owners.py

Issue 12326151: Add a way to require approval from owners other than the author. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: fix wording in comment Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | presubmit_canned_checks.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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)
OLDNEW
« no previous file with comments | « no previous file | presubmit_canned_checks.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698