Index: owners.py |
diff --git a/owners.py b/owners.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..76fb51f0528c1dea6cce88bb3417371e01c0c5f9 |
--- /dev/null |
+++ b/owners.py |
@@ -0,0 +1,90 @@ |
+# Copyright (c) 2010 The Chromium Authors. All rights reserved. |
M-A Ruel
2011/02/25 20:41:54
2011
|
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""A database of OWNERS files.""" |
+ |
+class Assertion(AssertionError): |
+ pass |
+ |
+class Database(object): |
+ def __init__(self, root, fopen, os_path): |
+ """Initializes the database of owners for a repository. |
M-A Ruel
2011/02/25 20:41:54
Personal nit: constructor docstring are usually us
|
+ |
+ Args: |
+ root: the path to the root of the Repository |
+ all_owners: the list of every owner in the system |
+ open: function callback to open a text file for reading |
+ os_path: module/object callback with fields for 'exists', |
+ 'dirname', and 'join' |
+ """ |
+ self.root = root |
+ self.fopen = fopen |
+ self.os_path = os_path |
+ |
+ # Mapping of files to authorized owners. |
+ self.files_owned_by = {} |
+ |
+ # Mapping of owners to the files they own. |
+ self.owners_for = {} |
+ |
+ # In-memory cached map of files to their OWNERS files. |
+ self.owners_file_for = {} |
+ |
+ # In-memory cache of OWNERS files and their contents |
+ self.owners_files = {} |
+ |
+ def OwnersFor(self, files): |
+ """Returns a sets of reviewers that will cover the set of files. |
+ |
+ The set of files are paths relative to (and under) self.root.""" |
+ self._LoadDataNeededFor(files) |
+ return self._CoveringSetOfOwnersFor(files) |
+ |
+ def FilesAreCoveredBy(self, files, reviewers): |
+ return not self.FilesNotCoveredBy(files, reviewers) |
+ |
+ def FilesNotCoveredBy(self, files, reviewers): |
+ covered_files = set() |
+ for reviewer in reviewers: |
+ covered_files = covered_files.union(self.files_owned_by[reviewer]) |
+ return files.difference(covered_files) |
+ |
+ def _LoadDataNeededFor(self, files): |
+ for f in files: |
+ self._LoadOwnersFor(f) |
+ |
+ def _LoadOwnersFor(self, f): |
M-A Ruel
2011/02/25 20:41:54
Is this function really needed? It's called only o
|
+ if f not in self.owners_for: |
+ owner_file = self._FindOwnersFileFor(f) |
+ self.owners_file_for[f] = owner_file |
+ self._ReadOwnersFile(owner_file, f) |
+ |
+ def _FindOwnersFileFor(self, f): |
+ # This is really a "do ... until dirname = ''" |
+ dirname = self.os_path.dirname(f) |
+ while dirname: |
+ owner_path = self.os_path.join(dirname, 'OWNERS') |
+ if self.os_path.exists(owner_path): |
+ return owner_path |
+ dirname = self.os_path.dirname(dirname) |
+ owner_path = self.os_path.join(dirname, 'OWNERS') |
+ if self.os_path.exists(owner_path): |
+ return owner_path |
+ raise Assertion('No OWNERS file found for %s' % f) |
+ |
+ def _ReadOwnersFile(self, owner_file, affected_file): |
+ owners_for = self.owners_for.setdefault(affected_file, set()) |
+ for owner in self.fopen(owner_file): |
+ owner = owner.strip() |
+ self.files_owned_by.setdefault(owner, set()).add(affected_file) |
+ owners_for.add(owner) |
+ |
+ def _CoveringSetOfOwnersFor(self, files): |
+ # TODO(dpranke): implement the greedy algorithm for covering sets, and |
+ # consider returning multiple options in case there are several equally |
+ # short combinations of owners. |
+ every_owner = set() |
+ for f in files: |
+ every_owner = every_owner.union(self.owners_for[f]) |
+ return every_owner |