owners-client: Add support for excluding owners.
It is useful to exclude the change author when suggesting owners.
Change-Id: I3321c013271f6cea1098abba59b509800818917e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/2669680
Commit-Queue: Edward Lesmes <ehmaldonado@chromium.org>
Auto-Submit: Edward Lesmes <ehmaldonado@chromium.org>
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
diff --git a/owners_client.py b/owners_client.py
index 0d2f505..77d4d7d 100644
--- a/owners_client.py
+++ b/owners_client.py
@@ -104,12 +104,15 @@
status[path] = self.INSUFFICIENT_REVIEWERS
return status
- def ScoreOwners(self, paths):
+ def ScoreOwners(self, paths, exclude=None):
"""Get sorted list of owners for the given paths."""
+ exclude = exclude or []
positions_by_owner = {}
owners_by_path = self.BatchListOwners(paths)
for owners in owners_by_path.values():
for i, owner in enumerate(owners):
+ if owner in exclude:
+ continue
# Gerrit API lists owners of a path sorted by an internal score, so
# owners that appear first should be prefered.
# We define the score of an owner based on the pair
@@ -124,22 +127,26 @@
key=lambda o: (-len(positions_by_owner[o]),
min(positions_by_owner[o]) + random.random()))
- def SuggestOwners(self, paths):
+ def SuggestOwners(self, paths, exclude=None):
"""Suggest a set of owners for the given paths."""
+ exclude = exclude or []
paths_by_owner = {}
owners_by_path = self.BatchListOwners(paths)
for path, owners in owners_by_path.items():
for owner in owners:
- paths_by_owner.setdefault(owner, set()).add(path)
+ if owner not in exclude:
+ paths_by_owner.setdefault(owner, set()).add(path)
# Select the minimum number of owners that can approve all paths.
# We start at 2 to avoid sending all changes that require multiple
# reviewers to top-level owners.
- owners = self.ScoreOwners(paths)
+ owners = self.ScoreOwners(paths, exclude=exclude)
if len(owners) < 2:
return owners
- for num_owners in range(2, len(owners)):
+ # Note that we have to iterate up to len(owners) + 1.
+ # e.g. if there are only 2 owners, we should consider num_owners = 2.
+ for num_owners in range(2, len(owners) + 1):
# Iterate all combinations of `num_owners` by decreasing score, and
# select the first one that covers all paths.
for selected in _owner_combinations(owners, num_owners):
@@ -147,6 +154,8 @@
if len(covered) == len(paths):
return list(selected)
+ return []
+
class DepotToolsClient(OwnersClient):
"""Implement OwnersClient using owners.py Database."""
diff --git a/tests/owners_client_test.py b/tests/owners_client_test.py
index 4f727b3..cf9dfcc 100644
--- a/tests/owners_client_test.py
+++ b/tests/owners_client_test.py
@@ -166,6 +166,17 @@
)
self.client.owners_by_path = {
+ 'a': [alice, bob],
+ 'b': [bob],
+ 'c': [bob, chris]
+ }
+ self.assertEqual(
+ self.client.ScoreOwners(
+ self.client.owners_by_path.keys(), exclude=[chris]),
+ [bob, alice],
+ )
+
+ self.client.owners_by_path = {
'a': [alice, bob, chris, dave],
'b': [chris, bob, dave],
'c': [chris, dave],
@@ -187,6 +198,11 @@
sorted(self.client.SuggestOwners(['abcd'])),
[alice, bob])
+ self.client.owners_by_path = {'abcd': [alice, bob, chris, dave]}
+ self.assertEqual(
+ sorted(self.client.SuggestOwners(['abcd'], exclude=[alice, bob])),
+ [chris, dave])
+
self.client.owners_by_path = {
'ae': [alice, emily],
'be': [bob, emily],