blob: 76fb51f0528c1dea6cce88bb3417371e01c0c5f9 [file] [log] [blame]
dpranke@chromium.org2a009622011-03-01 02:43:31 +00001# 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
3# found in the LICENSE file.
4
5"""A database of OWNERS files."""
6
7class Assertion(AssertionError):
8 pass
9
10class Database(object):
11 def __init__(self, root, fopen, os_path):
12 """Initializes the database of owners for a repository.
13
14 Args:
15 root: the path to the root of the Repository
16 all_owners: the list of every owner in the system
17 open: function callback to open a text file for reading
18 os_path: module/object callback with fields for 'exists',
19 'dirname', and 'join'
20 """
21 self.root = root
22 self.fopen = fopen
23 self.os_path = os_path
24
25 # Mapping of files to authorized owners.
26 self.files_owned_by = {}
27
28 # Mapping of owners to the files they own.
29 self.owners_for = {}
30
31 # In-memory cached map of files to their OWNERS files.
32 self.owners_file_for = {}
33
34 # In-memory cache of OWNERS files and their contents
35 self.owners_files = {}
36
37 def OwnersFor(self, files):
38 """Returns a sets of reviewers that will cover the set of files.
39
40 The set of files are paths relative to (and under) self.root."""
41 self._LoadDataNeededFor(files)
42 return self._CoveringSetOfOwnersFor(files)
43
44 def FilesAreCoveredBy(self, files, reviewers):
45 return not self.FilesNotCoveredBy(files, reviewers)
46
47 def FilesNotCoveredBy(self, files, reviewers):
48 covered_files = set()
49 for reviewer in reviewers:
50 covered_files = covered_files.union(self.files_owned_by[reviewer])
51 return files.difference(covered_files)
52
53 def _LoadDataNeededFor(self, files):
54 for f in files:
55 self._LoadOwnersFor(f)
56
57 def _LoadOwnersFor(self, f):
58 if f not in self.owners_for:
59 owner_file = self._FindOwnersFileFor(f)
60 self.owners_file_for[f] = owner_file
61 self._ReadOwnersFile(owner_file, f)
62
63 def _FindOwnersFileFor(self, f):
64 # This is really a "do ... until dirname = ''"
65 dirname = self.os_path.dirname(f)
66 while dirname:
67 owner_path = self.os_path.join(dirname, 'OWNERS')
68 if self.os_path.exists(owner_path):
69 return owner_path
70 dirname = self.os_path.dirname(dirname)
71 owner_path = self.os_path.join(dirname, 'OWNERS')
72 if self.os_path.exists(owner_path):
73 return owner_path
74 raise Assertion('No OWNERS file found for %s' % f)
75
76 def _ReadOwnersFile(self, owner_file, affected_file):
77 owners_for = self.owners_for.setdefault(affected_file, set())
78 for owner in self.fopen(owner_file):
79 owner = owner.strip()
80 self.files_owned_by.setdefault(owner, set()).add(affected_file)
81 owners_for.add(owner)
82
83 def _CoveringSetOfOwnersFor(self, files):
84 # TODO(dpranke): implement the greedy algorithm for covering sets, and
85 # consider returning multiple options in case there are several equally
86 # short combinations of owners.
87 every_owner = set()
88 for f in files:
89 every_owner = every_owner.union(self.owners_for[f])
90 return every_owner