Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2010 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 import collections | |
| 7 import re | 8 import re |
| 8 | 9 |
| 9 | 10 |
| 10 # If this is present by itself on a line, this means that everyone can review. | 11 # If this is present by itself on a line, this means that everyone can review. |
| 11 EVERYONE = '*' | 12 EVERYONE = '*' |
| 12 | 13 |
| 13 | 14 |
| 14 # Recognizes 'X@Y' email addresses. Very simplistic. | 15 # Recognizes 'X@Y' email addresses. Very simplistic. |
| 15 BASIC_EMAIL_REGEXP = r'^[\w\-\+\%\.]+\@[\w\-\+\%\.]+$' | 16 BASIC_EMAIL_REGEXP = r'^[\w\-\+\%\.]+\@[\w\-\+\%\.]+$' |
| 16 | 17 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 51 self.owned_by = {EVERYONE: set()} | 52 self.owned_by = {EVERYONE: set()} |
| 52 | 53 |
| 53 # Mapping of paths to authorized owners. | 54 # Mapping of paths to authorized owners. |
| 54 self.owners_for = {} | 55 self.owners_for = {} |
| 55 | 56 |
| 56 # Set of paths that stop us from looking above them for owners. | 57 # Set of paths that stop us from looking above them for owners. |
| 57 # (This is implicitly true for the root directory). | 58 # (This is implicitly true for the root directory). |
| 58 self.stop_looking = set(['']) | 59 self.stop_looking = set(['']) |
| 59 | 60 |
| 60 def reviewers_for(self, files): | 61 def reviewers_for(self, files): |
| 61 """Returns a suggested set of reviewers that will cover the set of files. | 62 """Returns a suggested set of reviewers that will cover the files. |
| 62 | 63 |
| 63 files is a set of paths relative to (and under) self.root.""" | 64 files is a sequence of paths relative to (and under) self.root.""" |
| 64 self._check_paths(files) | 65 self._check_paths(files) |
| 65 self._load_data_needed_for(files) | 66 self._load_data_needed_for(files) |
| 66 return self._covering_set_of_owners_for(files) | 67 return self._covering_set_of_owners_for(files) |
| 67 | 68 |
| 68 def files_are_covered_by(self, files, reviewers): | 69 def files_are_covered_by(self, files, reviewers): |
| 69 """Returns whether every file is owned by at least one reviewer.""" | 70 """Returns whether every file is owned by at least one reviewer.""" |
| 70 return not self.files_not_covered_by(files, reviewers) | 71 return not self.files_not_covered_by(files, reviewers) |
| 71 | 72 |
| 72 def files_not_covered_by(self, files, reviewers): | 73 def files_not_covered_by(self, files, reviewers): |
| 73 """Returns the set of files that are not owned by at least one reviewer.""" | 74 """Returns the set of files that are not owned by at least one reviewer. |
| 75 | |
| 76 Args: | |
| 77 files is a sequence of paths relative to (and under) self.root. | |
| 78 reviewers is a sequence of strings matching self.email_regexp.""" | |
| 74 self._check_paths(files) | 79 self._check_paths(files) |
| 75 self._check_reviewers(reviewers) | 80 self._check_reviewers(reviewers) |
| 76 if not reviewers: | 81 if not reviewers: |
| 77 return files | 82 return files |
| 78 | 83 |
| 79 self._load_data_needed_for(files) | 84 self._load_data_needed_for(files) |
| 80 files_by_dir = self._files_by_dir(files) | 85 files_by_dir = self._files_by_dir(files) |
| 81 covered_dirs = self._dirs_covered_by(reviewers) | 86 covered_dirs = self._dirs_covered_by(reviewers) |
| 82 uncovered_files = [] | 87 uncovered_files = [] |
| 83 for d, files_in_d in files_by_dir.iteritems(): | 88 for d, files_in_d in files_by_dir.iteritems(): |
| 84 if not self._is_dir_covered_by(d, covered_dirs): | 89 if not self._is_dir_covered_by(d, covered_dirs): |
| 85 uncovered_files.extend(files_in_d) | 90 uncovered_files.extend(files_in_d) |
| 86 return set(uncovered_files) | 91 return set(uncovered_files) |
| 87 | 92 |
| 93 def _check_collection(self, obj): | |
|
M-A Ruel
2011/03/17 01:27:47
I prefer this function to not be a member function
| |
| 94 assert (isinstance(obj, collections.Iterable) and | |
|
M-A Ruel
2011/03/17 01:27:47
assert (isinstance(obj, (collections.Iterable, col
| |
| 95 isinstance(obj, collections.Sized) and | |
| 96 not isinstance(obj, basestring)) | |
| 97 | |
| 88 def _check_paths(self, files): | 98 def _check_paths(self, files): |
| 89 def _is_under(f, pfx): | 99 def _is_under(f, pfx): |
| 90 return self.os_path.abspath(self.os_path.join(pfx, f)).startswith(pfx) | 100 return self.os_path.abspath(self.os_path.join(pfx, f)).startswith(pfx) |
| 101 self._check_collection(files) | |
| 91 assert all(_is_under(f, self.os_path.abspath(self.root)) for f in files) | 102 assert all(_is_under(f, self.os_path.abspath(self.root)) for f in files) |
| 92 | 103 |
| 93 def _check_reviewers(self, reviewers): | 104 def _check_reviewers(self, reviewers): |
| 94 """Verifies each reviewer is a valid email address.""" | 105 self._check_collection(reviewers) |
| 95 assert all(self.email_regexp.match(r) for r in reviewers) | 106 assert all(self.email_regexp.match(r) for r in reviewers) |
| 96 | 107 |
| 97 def _files_by_dir(self, files): | 108 def _files_by_dir(self, files): |
| 98 dirs = {} | 109 dirs = {} |
| 99 for f in files: | 110 for f in files: |
| 100 dirs.setdefault(self.os_path.dirname(f), []).append(f) | 111 dirs.setdefault(self.os_path.dirname(f), []).append(f) |
| 101 return dirs | 112 return dirs |
| 102 | 113 |
| 103 def _dirs_covered_by(self, reviewers): | 114 def _dirs_covered_by(self, reviewers): |
| 104 dirs = self.owned_by[EVERYONE] | 115 dirs = self.owned_by[EVERYONE] |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 154 # short combinations of owners. | 165 # short combinations of owners. |
| 155 every_owner = set() | 166 every_owner = set() |
| 156 for f in files: | 167 for f in files: |
| 157 dirname = self.os_path.dirname(f) | 168 dirname = self.os_path.dirname(f) |
| 158 while dirname in self.owners_for: | 169 while dirname in self.owners_for: |
| 159 every_owner |= self.owners_for[dirname] | 170 every_owner |= self.owners_for[dirname] |
| 160 if self._stop_looking(dirname): | 171 if self._stop_looking(dirname): |
| 161 break | 172 break |
| 162 dirname = self.os_path.dirname(dirname) | 173 dirname = self.os_path.dirname(dirname) |
| 163 return every_owner | 174 return every_owner |
| OLD | NEW |