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

Side by Side Diff: owners.py

Issue 11645009: Try again to land the improved owners algorithm. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: update w/ review feedback Created 8 years 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 | tests/owners_unittest.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 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
42 apply (up until a "set noparent is encountered"). 42 apply (up until a "set noparent is encountered").
43 43
44 If "per-file glob=set noparent" is used, then global directives are ignored 44 If "per-file glob=set noparent" is used, then global directives are ignored
45 for the glob, and only the "per-file" owners are used for files matching that 45 for the glob, and only the "per-file" owners are used for files matching that
46 glob. 46 glob.
47 47
48 Examples for all of these combinations can be found in tests/owners_unittest.py. 48 Examples for all of these combinations can be found in tests/owners_unittest.py.
49 """ 49 """
50 50
51 import collections 51 import collections
52 import random
52 import re 53 import re
53 54
54 55
55 # If this is present by itself on a line, this means that everyone can review. 56 # If this is present by itself on a line, this means that everyone can review.
56 EVERYONE = '*' 57 EVERYONE = '*'
57 58
58 59
59 # Recognizes 'X@Y' email addresses. Very simplistic. 60 # Recognizes 'X@Y' email addresses. Very simplistic.
60 BASIC_EMAIL_REGEXP = r'^[\w\-\+\%\.]+\@[\w\-\+\%\.]+$' 61 BASIC_EMAIL_REGEXP = r'^[\w\-\+\%\.]+\@[\w\-\+\%\.]+$'
61 62
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after
245 if directive == "set noparent": 246 if directive == "set noparent":
246 self.stop_looking.add(path) 247 self.stop_looking.add(path)
247 elif self.email_regexp.match(directive) or directive == EVERYONE: 248 elif self.email_regexp.match(directive) or directive == EVERYONE:
248 self.owned_by.setdefault(directive, set()).add(path) 249 self.owned_by.setdefault(directive, set()).add(path)
249 self.owners_for.setdefault(path, set()).add(directive) 250 self.owners_for.setdefault(path, set()).add(directive)
250 else: 251 else:
251 raise SyntaxErrorInOwnersFile(owners_path, lineno, 252 raise SyntaxErrorInOwnersFile(owners_path, lineno,
252 ('%s is not a "set" directive, "*", ' 253 ('%s is not a "set" directive, "*", '
253 'or an email address: "%s"' % (line_type, directive))) 254 'or an email address: "%s"' % (line_type, directive)))
254 255
256 def _covering_set_of_owners_for(self, files):
257 dirs_remaining = set(self._enclosing_dir_with_owners(f) for f in files)
258 all_possible_owners = self._all_possible_owners(dirs_remaining)
259 suggested_owners = set()
260 while dirs_remaining:
261 owner = self.lowest_cost_owner(all_possible_owners, dirs_remaining)
262 suggested_owners.add(owner)
263 for dirname, _ in all_possible_owners[owner]:
264 dirs_remaining.remove(dirname)
265 return suggested_owners
255 266
256 def _covering_set_of_owners_for(self, files): 267 def _all_possible_owners(self, dirs):
257 # Get the set of directories from the files. 268 """Returns a list of (potential owner, distance-from-dir) tuples; a
258 dirs = set() 269 distance of 1 is the lowest/closest possible distance (which makes the
259 for f in files: 270 subsequent math easier)."""
260 dirs.add(self._enclosing_dir_with_owners(f)) 271 all_possible_owners = {}
261
262
263 owned_dirs = {}
264 dir_owners = {}
265
266 for current_dir in dirs: 272 for current_dir in dirs:
267 # Get the list of owners for each directory.
268 current_owners = set()
269 dirname = current_dir 273 dirname = current_dir
270 while dirname in self.owners_for: 274 distance = 1
271 current_owners |= self.owners_for[dirname] 275 while True:
276 for owner in self.owners_for.get(dirname, []):
277 all_possible_owners.setdefault(owner, [])
278 # It's possible the same owner might match a directory from
279 # multiple files, and we only want the closest entry.
280 if not any(current_dir == el[0] for el in all_possible_owners[owner]):
281 all_possible_owners[owner].append((current_dir, distance))
272 if self._stop_looking(dirname): 282 if self._stop_looking(dirname):
273 break 283 break
274 prev_parent = dirname
275 dirname = self.os_path.dirname(dirname) 284 dirname = self.os_path.dirname(dirname)
276 if prev_parent == dirname: 285 distance += 1
277 break 286 return all_possible_owners
278 287
279 # Map each directory to a list of its owners. 288 @staticmethod
280 dir_owners[current_dir] = current_owners 289 def lowest_cost_owner(all_possible_owners, dirs):
290 # We want to minimize both the number of reviewers and the distance
291 # from the files/dirs needing reviews. The "pow(X, 1.75)" below is
292 # an arbitrarily-selected scaling factor that seems to work well - it
293 # will select one reviewer in the parent directory over three reviewers
294 # in subdirs, but not one reviewer over just two.
295 total_costs_by_owner = {}
296 for owner in all_possible_owners:
297 total_distance = 0
298 num_directories_owned = 0
299 for dirname, distance in all_possible_owners[owner]:
300 if dirname in dirs:
301 total_distance += distance
302 num_directories_owned += 1
303 if num_directories_owned:
304 total_costs_by_owner[owner] = (total_distance /
305 pow(num_directories_owned, 1.75))
281 306
282 # Add the directory to the list of each owner. 307 # Return the lowest cost owner. In the case of a tie, pick one randomly.
283 for owner in current_owners: 308 lowest_cost = min(total_costs_by_owner.itervalues())
284 owned_dirs.setdefault(owner, set()).add(current_dir) 309 lowest_cost_owners = filter(
285 310 lambda owner: total_costs_by_owner[owner] == lowest_cost,
286 final_owners = set() 311 total_costs_by_owner)
287 while dirs: 312 return random.Random().choice(lowest_cost_owners)
288 # Find the owner that has the most directories.
289 max_count = 0
290 max_owner = None
291 owner_count = {}
292 for dirname in dirs:
293 for owner in dir_owners[dirname]:
294 count = owner_count.get(owner, 0) + 1
295 owner_count[owner] = count
296 if count >= max_count:
297 max_owner = owner
298 max_count = count
299
300 # If no more directories have OWNERS, we're done.
301 if not max_owner:
302 break
303
304 final_owners.add(max_owner)
305
306 # Remove all directories owned by the current owner from the remaining
307 # list.
308 for dirname in owned_dirs[max_owner]:
309 dirs.discard(dirname)
310
311 return final_owners
OLDNEW
« no previous file with comments | « no previous file | tests/owners_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698