Skip to content
This repository was archived by the owner on Mar 14, 2023. It is now read-only.

Commit b78bd99

Browse files
committed
Match the longest dirs entries.
1 parent 33b005f commit b78bd99

File tree

3 files changed

+107
-27
lines changed

3 files changed

+107
-27
lines changed

highfive/newpr.py

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -274,44 +274,47 @@ def choose_reviewer(self, repo, owner, diff, exclude):
274274
dirs = self.repo_config.get('dirs', {})
275275
groups = self.get_groups()
276276

277-
most_changed = None
277+
# Map of `dirs` path to the number of changes found in that path.
278+
counts = {}
278279
# If there's directories with specially assigned groups/users
279280
# inspect the diff to find the directory with the most additions
280281
if dirs:
281-
counts = {}
282-
cur_dir = None
282+
# List of the longest `dirs` paths that match the current path.
283+
# This is a list to handle the situation if multiple paths of the
284+
# same length match.
285+
longest_dir_paths = []
283286
for line in diff.split('\n'):
284287
if line.startswith("diff --git "):
285-
# update cur_dir
286-
cur_dir = None
288+
# update longest_dir_paths
289+
longest_dir_paths = []
287290
parts = line[line.find(" b/") + len(" b/"):].split("/")
288291
if not parts:
289292
continue
290-
cur_dir = "/".join(parts[:2])
291-
292-
# A few heuristics to get better reviewers
293-
if cur_dir.startswith('compiler/'):
294-
cur_dir = 'compiler'
295-
if cur_dir == 'src/test':
296-
cur_dir = None
297-
if cur_dir and cur_dir not in counts:
298-
counts[cur_dir] = 0
299-
continue
300293

301-
if cur_dir and (not line.startswith('+++')) and line.startswith('+'):
302-
counts[cur_dir] += 1
294+
# Find the longest `dirs` entries that match this path.
295+
longest = {}
296+
for dir_path in dirs:
297+
dir_parts = dir_path.split('/')
298+
if parts[:len(dir_parts)] == dir_parts:
299+
longest[dir_path] = len(dir_parts)
300+
max_count = max(longest.values(), default=0)
301+
longest_dir_paths = [
302+
path for (path, count) in longest.items()
303+
if count == max_count
304+
]
305+
continue
303306

304-
# Find the largest count.
305-
most_changes = 0
306-
for directory, changes in counts.items():
307-
if changes > most_changes:
308-
most_changes = changes
309-
most_changed = directory
307+
if (not line.startswith('+++')) and line.startswith('+'):
308+
for path in longest_dir_paths:
309+
counts[path] = counts.get(path, 0) + 1
310310

311-
# lookup that directory in the json file to find the potential reviewers
311+
# `all` is always included.
312312
potential = groups['all']
313-
if most_changed and most_changed in dirs:
314-
potential.extend(dirs[most_changed])
313+
# Include the `dirs` entries with the maximum number of matches.
314+
max_count = max(counts.values(), default=0)
315+
max_paths = [path for (path, count) in counts.items() if count == max_count]
316+
for path in max_paths:
317+
potential.extend(dirs[path])
315318
if not potential:
316319
potential = groups['core']
317320

highfive/tests/fakes.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,19 @@ def get_repo_configs():
8787
},
8888
'teams': {
8989
"groups": {"all": ["@ehuss"], "a": ["@pnkfelix"], "d": ["@e"], "compiler-team": ["@niko"], "b/c": ["@nrc"]}
90-
}
90+
},
91+
'prefixed-dirs': {
92+
"groups": {
93+
"all": [],
94+
"compiler": ["@compiler"],
95+
},
96+
"dirs": {
97+
"compiler": ["compiler"],
98+
"compiler/rustc_llvm": ["@llvm"],
99+
"compiler/rustc_parse": ["@parser"],
100+
"compiler/rustc_parse/src/parse/lexer": ["@lexer"],
101+
}
102+
},
91103
}
92104

93105

@@ -127,3 +139,28 @@ def new_pr(
127139
p['pull_request']['user']['login'] = pr_author
128140

129141
return payload.Payload(p)
142+
143+
144+
def make_fake_diff(paths):
145+
"""Generates a fake diff that touches the given files.
146+
147+
:param paths: A sequence of `(path, added, removed)` tuples where `added`
148+
is the number of lines added, and `removed` is the number of lines
149+
removed.
150+
:returns: A string of the fake diff.
151+
"""
152+
# This isn't a properly structured diff, but it has approximately enough
153+
# information for what highfive looks at.
154+
result = []
155+
for (path, added, removed) in paths:
156+
result.append(f'diff --git a/{path} b/{path}')
157+
result.append('index 1677422122e..1108c1f4d4c 100644')
158+
result.append(f'--- a/{path}')
159+
result.append(f'+++ b/{path}')
160+
result.append('@@ -0,0 +1 @@')
161+
for n in range(added):
162+
result.append(f'+Added line {n}')
163+
for n in range(removed):
164+
result.append(f'-Removed line {n}')
165+
result.append('')
166+
return '\n'.join(result)

highfive/tests/test_newpr.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,46 @@ def test_with_team_ping(self):
13101310
assert handler.find_reviewer(msg, None) is None, \
13111311
"expected '%s' to have no reviewer extracted" % msg
13121312

1313+
def test_prefixed_dirs(self):
1314+
"""Test dirs with multiple overlapping prefixes."""
1315+
self.handler = HighfiveHandlerMock(
1316+
Payload({}), repo_config=self.fakes['config']['prefixed-dirs']
1317+
).handler
1318+
# Base compiler rule should catch anything in compiler/
1319+
diff = fakes.make_fake_diff([('compiler/foo', 1, 1)])
1320+
(chosen_reviewers, _) = self.choose_reviewers(diff, 'ehuss')
1321+
assert set(['compiler']) == chosen_reviewers
1322+
1323+
# Anything in rustc_llvm should go to llvm.
1324+
diff = fakes.make_fake_diff([('compiler/rustc_llvm/foo', 1, 1)])
1325+
(chosen_reviewers, _) = self.choose_reviewers(diff, 'ehuss')
1326+
assert set(['llvm']) == chosen_reviewers
1327+
1328+
# 1 change in rustc_llvm, multiple changes in other directories, the
1329+
# other directories win because they have more changes.
1330+
diff = fakes.make_fake_diff([
1331+
('compiler/rustc_llvm/foo', 1, 1),
1332+
('compiler/rustc_traits/src/foo.rs', 0, 1),
1333+
('compiler/rustc_macros//src/foo.rs', 2, 3),
1334+
])
1335+
(chosen_reviewers, _) = self.choose_reviewers(diff, 'ehuss')
1336+
assert set(['compiler']) == chosen_reviewers
1337+
1338+
# Change in a deeply nested directory should win over its parent.
1339+
diff = fakes.make_fake_diff([
1340+
('compiler/rustc_parse/src/parse/lexer/foo.rs', 1, 1)
1341+
])
1342+
(chosen_reviewers, _) = self.choose_reviewers(diff, 'ehuss')
1343+
assert set(['lexer']) == chosen_reviewers
1344+
1345+
# Most changes in one component should win over the base compiler.
1346+
diff = fakes.make_fake_diff([
1347+
('compiler/rustc_parse/src/foo.rs', 5, 10),
1348+
('compiler/rustc_llvm/src/foo.rs', 1, 1),
1349+
])
1350+
(chosen_reviewers, _) = self.choose_reviewers(diff, 'ehuss')
1351+
assert set(['parser']) == chosen_reviewers
1352+
13131353

13141354
class TestRun(TestNewPR):
13151355
@pytest.fixture(autouse=True)

0 commit comments

Comments
 (0)