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

Side by Side Diff: owners.py

Issue 2293233002: owners.py: partial fix for owners-check perf regression (Closed)
Patch Set: Ready for upload. Created 4 years, 3 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
« no previous file with comments | « no previous file | no next file » | 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 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
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
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
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)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698