| 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 re | 7 import re |
| 8 | 8 |
| 9 | 9 |
| 10 # If this is present by itself on a line, this means that everyone can review. | 10 # If this is present by itself on a line, this means that everyone can review. |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 54 # Mapping of owners to the paths they own. | 54 # Mapping of owners to the paths they own. |
| 55 self.owned_by = {EVERYONE: set()} | 55 self.owned_by = {EVERYONE: set()} |
| 56 | 56 |
| 57 # Mapping of paths to authorized owners. | 57 # Mapping of paths to authorized owners. |
| 58 self.owners_for = {} | 58 self.owners_for = {} |
| 59 | 59 |
| 60 # Set of paths that stop us from looking above them for owners. | 60 # Set of paths that stop us from looking above them for owners. |
| 61 # (This is implicitly true for the root directory). | 61 # (This is implicitly true for the root directory). |
| 62 self.stop_looking = set(['']) | 62 self.stop_looking = set(['']) |
| 63 | 63 |
| 64 def ReviewersFor(self, files): | 64 def reviewers_for(self, files): |
| 65 """Returns a suggested set of reviewers that will cover the set of files. | 65 """Returns a suggested set of reviewers that will cover the set of files. |
| 66 | 66 |
| 67 files is a set of paths relative to (and under) self.root.""" | 67 files is a set of paths relative to (and under) self.root.""" |
| 68 self._CheckPaths(files) | 68 self._check_paths(files) |
| 69 self._LoadDataNeededFor(files) | 69 self._load_data_needed_for(files) |
| 70 return self._CoveringSetOfOwnersFor(files) | 70 return self._covering_set_of_owners_for(files) |
| 71 | 71 |
| 72 def FilesAreCoveredBy(self, files, reviewers): | 72 def files_are_covered_by(self, files, reviewers): |
| 73 """Returns whether every file is owned by at least one reviewer.""" | 73 """Returns whether every file is owned by at least one reviewer.""" |
| 74 return not self.FilesNotCoveredBy(files, reviewers) | 74 return not self.files_not_covered_by(files, reviewers) |
| 75 | 75 |
| 76 def FilesNotCoveredBy(self, files, reviewers): | 76 def files_not_covered_by(self, files, reviewers): |
| 77 """Returns the set of files that are not owned by at least one reviewer.""" | 77 """Returns the set of files that are not owned by at least one reviewer.""" |
| 78 self._CheckPaths(files) | 78 self._check_paths(files) |
| 79 self._CheckReviewers(reviewers) | 79 self._check_reviewers(reviewers) |
| 80 if not reviewers: | 80 if not reviewers: |
| 81 return files | 81 return files |
| 82 | 82 |
| 83 self._LoadDataNeededFor(files) | 83 self._load_data_needed_for(files) |
| 84 files_by_dir = self._FilesByDir(files) | 84 files_by_dir = self._files_by_dir(files) |
| 85 covered_dirs = self._DirsCoveredBy(reviewers) | 85 covered_dirs = self._dirs_covered_by(reviewers) |
| 86 uncovered_files = [] | 86 uncovered_files = [] |
| 87 for d, files_in_d in files_by_dir.iteritems(): | 87 for d, files_in_d in files_by_dir.iteritems(): |
| 88 if not self._IsDirCoveredBy(d, covered_dirs): | 88 if not self._is_dir_covered_by(d, covered_dirs): |
| 89 uncovered_files.extend(files_in_d) | 89 uncovered_files.extend(files_in_d) |
| 90 return set(uncovered_files) | 90 return set(uncovered_files) |
| 91 | 91 |
| 92 def _CheckPaths(self, files): | 92 def _check_paths(self, files): |
| 93 def _isunder(f, pfx): | 93 def _is_under(f, pfx): |
| 94 return self.os_path.abspath(self.os_path.join(pfx, f)).startswith(pfx) | 94 return self.os_path.abspath(self.os_path.join(pfx, f)).startswith(pfx) |
| 95 assert all(_isunder(f, self.os_path.abspath(self.root)) for f in files) | 95 assert all(_is_under(f, self.os_path.abspath(self.root)) for f in files) |
| 96 | 96 |
| 97 def _CheckReviewers(self, reviewers): | 97 def _check_reviewers(self, reviewers): |
| 98 """Verifies each reviewer is a valid email address.""" | 98 """Verifies each reviewer is a valid email address.""" |
| 99 assert all(self.email_regexp.match(r) for r in reviewers) | 99 assert all(self.email_regexp.match(r) for r in reviewers) |
| 100 | 100 |
| 101 def _FilesByDir(self, files): | 101 def _files_by_dir(self, files): |
| 102 dirs = {} | 102 dirs = {} |
| 103 for f in files: | 103 for f in files: |
| 104 dirs.setdefault(self.os_path.dirname(f), []).append(f) | 104 dirs.setdefault(self.os_path.dirname(f), []).append(f) |
| 105 return dirs | 105 return dirs |
| 106 | 106 |
| 107 def _DirsCoveredBy(self, reviewers): | 107 def _dirs_covered_by(self, reviewers): |
| 108 dirs = self.owned_by[EVERYONE] | 108 dirs = self.owned_by[EVERYONE] |
| 109 for r in reviewers: | 109 for r in reviewers: |
| 110 dirs = dirs | self.owned_by.get(r, set()) | 110 dirs = dirs | self.owned_by.get(r, set()) |
| 111 return dirs | 111 return dirs |
| 112 | 112 |
| 113 def _StopLooking(self, dirname): | 113 def _stop_looking(self, dirname): |
| 114 return dirname in self.stop_looking | 114 return dirname in self.stop_looking |
| 115 | 115 |
| 116 def _IsDirCoveredBy(self, dirname, covered_dirs): | 116 def _is_dir_covered_by(self, dirname, covered_dirs): |
| 117 while not dirname in covered_dirs and not self._StopLooking(dirname): | 117 while not dirname in covered_dirs and not self._stop_looking(dirname): |
| 118 dirname = self.os_path.dirname(dirname) | 118 dirname = self.os_path.dirname(dirname) |
| 119 return dirname in covered_dirs | 119 return dirname in covered_dirs |
| 120 | 120 |
| 121 def _LoadDataNeededFor(self, files): | 121 def _load_data_needed_for(self, files): |
| 122 for f in files: | 122 for f in files: |
| 123 dirpath = self.os_path.dirname(f) | 123 dirpath = self.os_path.dirname(f) |
| 124 while not dirpath in self.owners_for: | 124 while not dirpath in self.owners_for: |
| 125 self._ReadOwnersInDir(dirpath) | 125 self._read_owners_in_dir(dirpath) |
| 126 if self._StopLooking(dirpath): | 126 if self._stop_looking(dirpath): |
| 127 break | 127 break |
| 128 dirpath = self.os_path.dirname(dirpath) | 128 dirpath = self.os_path.dirname(dirpath) |
| 129 | 129 |
| 130 def _ReadOwnersInDir(self, dirpath): | 130 def _read_owners_in_dir(self, dirpath): |
| 131 owners_path = self.os_path.join(self.root, dirpath, 'OWNERS') | 131 owners_path = self.os_path.join(self.root, dirpath, 'OWNERS') |
| 132 if not self.os_path.exists(owners_path): | 132 if not self.os_path.exists(owners_path): |
| 133 return | 133 return |
| 134 | 134 |
| 135 lineno = 0 | 135 lineno = 0 |
| 136 for line in self.fopen(owners_path): | 136 for line in self.fopen(owners_path): |
| 137 lineno += 1 | 137 lineno += 1 |
| 138 line = line.strip() | 138 line = line.strip() |
| 139 if line.startswith('#'): | 139 if line.startswith('#'): |
| 140 continue | 140 continue |
| 141 if line == 'set noparent': | 141 if line == 'set noparent': |
| 142 self.stop_looking.add(dirpath) | 142 self.stop_looking.add(dirpath) |
| 143 continue | 143 continue |
| 144 if self.email_regexp.match(line) or line == EVERYONE: | 144 if self.email_regexp.match(line) or line == EVERYONE: |
| 145 self.owned_by.setdefault(line, set()).add(dirpath) | 145 self.owned_by.setdefault(line, set()).add(dirpath) |
| 146 self.owners_for.setdefault(dirpath, set()).add(line) | 146 self.owners_for.setdefault(dirpath, set()).add(line) |
| 147 continue | 147 continue |
| 148 raise SyntaxErrorInOwnersFile(owners_path, lineno, line) | 148 raise SyntaxErrorInOwnersFile(owners_path, lineno, line) |
| 149 | 149 |
| 150 def _CoveringSetOfOwnersFor(self, files): | 150 def _covering_set_of_owners_for(self, files): |
| 151 # TODO(dpranke): implement the greedy algorithm for covering sets, and | 151 # TODO(dpranke): implement the greedy algorithm for covering sets, and |
| 152 # consider returning multiple options in case there are several equally | 152 # consider returning multiple options in case there are several equally |
| 153 # short combinations of owners. | 153 # short combinations of owners. |
| 154 every_owner = set() | 154 every_owner = set() |
| 155 for f in files: | 155 for f in files: |
| 156 dirname = self.os_path.dirname(f) | 156 dirname = self.os_path.dirname(f) |
| 157 while dirname in self.owners_for: | 157 while dirname in self.owners_for: |
| 158 every_owner |= self.owners_for[dirname] | 158 every_owner |= self.owners_for[dirname] |
| 159 if self._StopLooking(dirname): | 159 if self._stop_looking(dirname): |
| 160 break | 160 break |
| 161 dirname = self.os_path.dirname(dirname) | 161 dirname = self.os_path.dirname(dirname) |
| 162 return every_owner | 162 return every_owner |
| OLD | NEW |